Merge "Add string linter preupload check" into pi-dev
diff --git a/res/xml/date_time_prefs.xml b/res/xml/date_time_prefs.xml
index 3249189..511b39f 100644
--- a/res/xml/date_time_prefs.xml
+++ b/res/xml/date_time_prefs.xml
@@ -30,7 +30,6 @@
             android:summaryOn="@string/date_time_auto_summaryOn"
             android:summaryOff="@string/date_time_auto_summaryOff"
             settings:useAdditionalSummary="true"
-            settings:restrictedSwitchSummary="@string/enabled_by_admin"
             settings:userRestriction="no_config_date_time" />
 
         <com.android.settingslib.RestrictedPreference
diff --git a/res/xml/zen_mode_block_settings.xml b/res/xml/zen_mode_block_settings.xml
index ce33e97..595e2ca 100644
--- a/res/xml/zen_mode_block_settings.xml
+++ b/res/xml/zen_mode_block_settings.xml
@@ -17,7 +17,8 @@
 
 <PreferenceScreen
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:key="zen_mode_block_settings_page">
+    android:key="zen_mode_block_settings_page"
+    android:title="@string/zen_mode_behavior_settings_title">
 
     <!-- sound vibration -->
     <CheckBoxPreference
diff --git a/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapter.java b/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapter.java
index effa948..5a614a3 100644
--- a/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapter.java
+++ b/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapter.java
@@ -18,6 +18,7 @@
 
 import android.icu.text.BreakIterator;
 import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
 import android.support.annotation.WorkerThread;
 import android.support.v7.widget.RecyclerView;
 import android.text.TextUtils;
@@ -42,14 +43,14 @@
         extends RecyclerView.Adapter<BaseTimeZoneAdapter.ItemViewHolder> {
 
     private final List<T> mOriginalItems;
-    private final OnListItemClickListener mOnListItemClickListener;
+    private final OnListItemClickListener<T> mOnListItemClickListener;
     private final Locale mLocale;
     private final boolean mShowItemSummary;
 
     private List<T> mItems;
     private ArrayFilter mFilter;
 
-    public BaseTimeZoneAdapter(List<T> items, OnListItemClickListener
+    public BaseTimeZoneAdapter(List<T> items, OnListItemClickListener<T>
             onListItemClickListener, Locale locale, boolean showItemSummary) {
         mOriginalItems = items;
         mItems = items;
@@ -69,14 +70,8 @@
 
     @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);
+        holder.setAdapterItem(mItems.get(position));
+        holder.mSummaryFrame.setVisibility(mShowItemSummary ? View.VISIBLE : View.GONE);
     }
 
     @Override
@@ -89,8 +84,7 @@
         return mItems.size();
     }
 
-    public  @NonNull
-    Filter getFilter() {
+    public @NonNull ArrayFilter getFilter() {
         if (mFilter == null) {
             mFilter = new ArrayFilter();
         }
@@ -110,18 +104,18 @@
         String[] getSearchKeys();
     }
 
-    public static class ItemViewHolder extends RecyclerView.ViewHolder
-            implements View.OnClickListener{
+    public static class ItemViewHolder<T extends BaseTimeZoneAdapter.AdapterItem>
+            extends RecyclerView.ViewHolder implements View.OnClickListener {
 
-        final OnListItemClickListener mOnListItemClickListener;
+        final OnListItemClickListener<T> mOnListItemClickListener;
         final View mSummaryFrame;
         final TextView mTitleView;
         final TextView mIconTextView;
         final TextView mSummaryView;
         final TextView mTimeView;
-        private int mPosition;
+        private T mItem;
 
-        public ItemViewHolder(View itemView, OnListItemClickListener onListItemClickListener) {
+        public ItemViewHolder(View itemView, OnListItemClickListener<T> onListItemClickListener) {
             super(itemView);
             itemView.setOnClickListener(this);
             mSummaryFrame = itemView.findViewById(R.id.summary_frame);
@@ -132,13 +126,17 @@
             mOnListItemClickListener = onListItemClickListener;
         }
 
-        public void setPosition(int position) {
-            mPosition = position;
+        public void setAdapterItem(T item) {
+            mItem = item;
+            mTitleView.setText(item.getTitle());
+            mIconTextView.setText(item.getIconText());
+            mSummaryView.setText(item.getSummary());
+            mTimeView.setText(item.getCurrentTime());
         }
 
         @Override
         public void onClick(View v) {
-            mOnListItemClickListener.onListItemClick(mPosition);
+            mOnListItemClickListener.onListItemClick(mItem);
         }
     }
 
@@ -151,7 +149,8 @@
      * require additional pre-processing. Potentially, a trie structure can be used to match
      * prefixes of the search keys.
      */
-    private class ArrayFilter extends Filter {
+    @VisibleForTesting
+    public class ArrayFilter extends Filter {
 
         private BreakIterator mBreakIterator = BreakIterator.getWordInstance(mLocale);
 
@@ -197,8 +196,9 @@
             return results;
         }
 
+        @VisibleForTesting
         @Override
-        protected void publishResults(CharSequence constraint, FilterResults results) {
+        public void publishResults(CharSequence constraint, FilterResults results) {
             mItems = (List<T>) results.values;
             notifyDataSetChanged();
         }
diff --git a/src/com/android/settings/datetime/timezone/BaseTimeZoneInfoPicker.java b/src/com/android/settings/datetime/timezone/BaseTimeZoneInfoPicker.java
index c1a3f8b..d734542 100644
--- a/src/com/android/settings/datetime/timezone/BaseTimeZoneInfoPicker.java
+++ b/src/com/android/settings/datetime/timezone/BaseTimeZoneInfoPicker.java
@@ -31,7 +31,6 @@
 import java.util.Date;
 import java.util.List;
 import java.util.Locale;
-import java.util.Objects;
 
 /**
  * Render a list of {@class TimeZoneInfo} into the list view in {@class BaseTimeZonePicker}
@@ -52,8 +51,8 @@
         return mAdapter;
     }
 
-    private void onListItemClick(int position) {
-        final TimeZoneInfo timeZoneInfo = mAdapter.getItem(position).mTimeZoneInfo;
+    private void onListItemClick(TimeZoneInfoItem item) {
+        final TimeZoneInfo timeZoneInfo = item.mTimeZoneInfo;
         getActivity().setResult(Activity.RESULT_OK, prepareResultData(timeZoneInfo));
         getActivity().finish();
     }
@@ -67,7 +66,7 @@
     protected static class ZoneAdapter extends BaseTimeZoneAdapter<TimeZoneInfoItem> {
 
         public ZoneAdapter(Context context, List<TimeZoneInfo> timeZones,
-                OnListItemClickListener onListItemClickListener, Locale locale) {
+                OnListItemClickListener<TimeZoneInfoItem> onListItemClickListener, Locale locale) {
             super(createTimeZoneInfoItems(context, timeZones, locale),
                     onListItemClickListener, locale,  true /* showItemSummary */);
         }
diff --git a/src/com/android/settings/datetime/timezone/BaseTimeZonePicker.java b/src/com/android/settings/datetime/timezone/BaseTimeZonePicker.java
index 032e2d2..5150045 100644
--- a/src/com/android/settings/datetime/timezone/BaseTimeZonePicker.java
+++ b/src/com/android/settings/datetime/timezone/BaseTimeZonePicker.java
@@ -17,6 +17,7 @@
 package com.android.settings.datetime.timezone;
 
 import android.os.Bundle;
+import android.support.annotation.VisibleForTesting;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
@@ -161,8 +162,8 @@
         return false;
     }
 
-    public interface OnListItemClickListener {
-        void onListItemClick(int position);
+    public interface OnListItemClickListener<T extends BaseTimeZoneAdapter.AdapterItem> {
+        void onListItemClick(T item);
     }
 
 }
diff --git a/src/com/android/settings/datetime/timezone/RegionSearchPicker.java b/src/com/android/settings/datetime/timezone/RegionSearchPicker.java
index bb87e85..ca4e0bc 100644
--- a/src/com/android/settings/datetime/timezone/RegionSearchPicker.java
+++ b/src/com/android/settings/datetime/timezone/RegionSearchPicker.java
@@ -18,15 +18,16 @@
 
 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.support.annotation.VisibleForTesting;
 import android.util.Log;
 
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.settings.R;
 import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.datetime.timezone.BaseTimeZoneAdapter.AdapterItem;
 import com.android.settings.datetime.timezone.model.FilteredCountryTimeZones;
 import com.android.settings.datetime.timezone.model.TimeZoneData;
 
@@ -63,8 +64,8 @@
         return mAdapter;
     }
 
-    private void onListItemClick(int position) {
-        final String regionId = mAdapter.getItem(position).getId();
+    private void onListItemClick(RegionItem item) {
+        final String regionId = item.getId();
         final FilteredCountryTimeZones countryTimeZones = mTimeZoneData.lookupCountryTimeZones(
                 regionId);
         final Activity activity = getActivity();
@@ -119,7 +120,8 @@
         return new ArrayList<>(items);
     }
 
-    private static class RegionItem implements BaseTimeZoneAdapter.AdapterItem {
+    @VisibleForTesting
+    static class RegionItem implements AdapterItem {
 
         private final String mId;
         private final String mName;
diff --git a/src/com/android/settings/fuelgauge/BatteryEntry.java b/src/com/android/settings/fuelgauge/BatteryEntry.java
index 7a18cd0..1733a6e 100644
--- a/src/com/android/settings/fuelgauge/BatteryEntry.java
+++ b/src/com/android/settings/fuelgauge/BatteryEntry.java
@@ -38,6 +38,7 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Locale;
 
 /**
  * Wraps the power usage data of a BatterySipper with information about package name
@@ -55,6 +56,8 @@
     static final ArrayList<BatteryEntry> mRequestQueue = new ArrayList<BatteryEntry>();
     static Handler sHandler;
 
+    static Locale sCurrentLocale = null;
+
     static private class NameAndIconLoader extends Thread {
         private boolean mAbort = false;
 
@@ -227,6 +230,13 @@
     }
 
     void getQuickNameIconForUid(final int uid) {
+        // Locale sync to system config in Settings
+        final Locale locale = Locale.getDefault();
+        if (sCurrentLocale != locale) {
+            clearUidCache();
+            sCurrentLocale = locale;
+        }
+
         final String uidString = Integer.toString(uid);
         if (sUidCache.containsKey(uidString)) {
             UidToDetail utd = sUidCache.get(uidString);
diff --git a/src/com/android/settings/wifi/CaptivePortalNetworkCallback.java b/src/com/android/settings/wifi/CaptivePortalNetworkCallback.java
new file mode 100644
index 0000000..9bcfba0
--- /dev/null
+++ b/src/com/android/settings/wifi/CaptivePortalNetworkCallback.java
@@ -0,0 +1,74 @@
+/*
+ * 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.wifi;
+
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+
+import com.android.internal.util.Preconditions;
+
+/** Listens for changes to NetworkCapabilities to update the ConnectedAccessPointPreference. */
+final class CaptivePortalNetworkCallback extends NetworkCallback {
+
+    private final ConnectedAccessPointPreference mConnectedApPreference;
+    private final Network mNetwork;
+
+    private boolean mIsCaptivePortal;
+
+    CaptivePortalNetworkCallback(
+            Network network, ConnectedAccessPointPreference connectedApPreference) {
+        mNetwork = Preconditions.checkNotNull(network);
+        mConnectedApPreference = Preconditions.checkNotNull(connectedApPreference);
+    }
+
+    @Override
+    public void onLost(Network network) {
+        if (mNetwork.equals(network)) {
+            mIsCaptivePortal = false;
+        }
+    }
+
+    @Override
+    public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
+        if (mNetwork.equals(network)) {
+            mIsCaptivePortal = WifiUtils.canSignIntoNetwork(networkCapabilities);
+            mConnectedApPreference.setCaptivePortal(mIsCaptivePortal);
+        }
+    }
+
+    /**
+     * Returns true if the supplied network and preference are not null and are the same as the
+     * originally supplied values.
+     */
+    public boolean isSameNetworkAndPreference(
+            Network network, ConnectedAccessPointPreference connectedApPreference) {
+        return mNetwork.equals(network) && mConnectedApPreference == connectedApPreference;
+    }
+
+    /**
+     * Returns true if the most recent update to the NetworkCapabilities indicates a captive portal
+     * network and the Network was not lost in the interim.
+     */
+    public boolean isCaptivePortal() {
+        return mIsCaptivePortal;
+    }
+
+    /** Returns the currently associated network. */
+    public Network getNetwork() {
+        return mNetwork;
+    }
+}
diff --git a/src/com/android/settings/wifi/ConnectedAccessPointPreference.java b/src/com/android/settings/wifi/ConnectedAccessPointPreference.java
index e9e1560..def48e0 100644
--- a/src/com/android/settings/wifi/ConnectedAccessPointPreference.java
+++ b/src/com/android/settings/wifi/ConnectedAccessPointPreference.java
@@ -31,15 +31,12 @@
 public class ConnectedAccessPointPreference extends AccessPointPreference implements
         View.OnClickListener {
 
-    private final CaptivePortalStatus mCaptivePortalStatus;
     private OnGearClickListener mOnGearClickListener;
-    private boolean mCaptivePortalNetwork;
+    private boolean mIsCaptivePortal;
 
     public ConnectedAccessPointPreference(AccessPoint accessPoint, Context context,
-            UserBadgeCache cache, @DrawableRes int iconResId, boolean forSavedNetworks,
-            CaptivePortalStatus captivePortalStatus) {
+            UserBadgeCache cache, @DrawableRes int iconResId, boolean forSavedNetworks) {
         super(accessPoint, context, cache, iconResId, forSavedNetworks);
-        mCaptivePortalStatus = captivePortalStatus;
     }
 
     @Override
@@ -51,9 +48,8 @@
     public void refresh() {
         super.refresh();
 
-        mCaptivePortalNetwork = mCaptivePortalStatus.isCaptivePortalNetwork();
-        setShowDivider(mCaptivePortalNetwork);
-        if (mCaptivePortalNetwork) {
+        setShowDivider(mIsCaptivePortal);
+        if (mIsCaptivePortal) {
             setSummary(R.string.wifi_tap_to_sign_in);
         }
     }
@@ -71,8 +67,8 @@
         gear.setOnClickListener(this);
 
         final View gearNoBg = holder.findViewById(R.id.settings_button_no_background);
-        gearNoBg.setVisibility(mCaptivePortalNetwork ? View.INVISIBLE : View.VISIBLE);
-        gear.setVisibility(mCaptivePortalNetwork ? View.VISIBLE : View.INVISIBLE);
+        gearNoBg.setVisibility(mIsCaptivePortal ? View.INVISIBLE : View.VISIBLE);
+        gear.setVisibility(mIsCaptivePortal ? View.VISIBLE : View.INVISIBLE);
     }
 
     @Override
@@ -84,11 +80,15 @@
         }
     }
 
+    public void setCaptivePortal(boolean isCaptivePortal) {
+        if (mIsCaptivePortal != isCaptivePortal) {
+            mIsCaptivePortal = isCaptivePortal;
+            refresh();
+        }
+    }
+
     public interface OnGearClickListener {
         void onGearClick(ConnectedAccessPointPreference p);
     }
 
-    public interface CaptivePortalStatus {
-        boolean isCaptivePortalNetwork();
-    }
 }
diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java
index 7c1b725..0f6a0bb 100644
--- a/src/com/android/settings/wifi/WifiSettings.java
+++ b/src/com/android/settings/wifi/WifiSettings.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.wifi;
 
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.os.UserManager.DISALLOW_CONFIG_WIFI;
 
 import android.annotation.NonNull;
@@ -27,14 +28,15 @@
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
 import android.net.Network;
-import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.State;
+import android.net.NetworkRequest;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 import android.nfc.NfcAdapter;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.PowerManager;
 import android.provider.Settings;
 import android.support.annotation.VisibleForTesting;
@@ -124,6 +126,7 @@
     private WifiManager.ActionListener mConnectListener;
     private WifiManager.ActionListener mSaveListener;
     private WifiManager.ActionListener mForgetListener;
+    private CaptivePortalNetworkCallback mCaptivePortalNetworkCallback;
 
     /**
      * The state of {@link #isUiRestricted()} at {@link #onCreate(Bundle)}}. This is neccesary to
@@ -400,6 +403,7 @@
     public void onStop() {
         getView().removeCallbacks(mUpdateAccessPointsRunnable);
         getView().removeCallbacks(mHideProgressBarRunnable);
+        unregisterCaptivePortalNetworkCallback();
         super.onStop();
     }
 
@@ -786,11 +790,9 @@
 
     @NonNull
     private ConnectedAccessPointPreference createConnectedAccessPointPreference(
-            AccessPoint accessPoint,
-            ConnectedAccessPointPreference.CaptivePortalStatus captivePortalStatus) {
+            AccessPoint accessPoint) {
         return new ConnectedAccessPointPreference(accessPoint, getPrefContext(), mUserBadgeCache,
-                R.drawable.ic_wifi_signal_0, false /* forSavedNetworks */,
-                captivePortalStatus);
+                R.drawable.ic_wifi_signal_0, false /* forSavedNetworks */);
     }
 
     /**
@@ -817,8 +819,9 @@
         }
 
         // Is the previous currently connected SSID different from the new one?
-        AccessPointPreference preference = (AccessPointPreference)
-            (mConnectedAccessPointPreferenceCategory.getPreference(0));
+        ConnectedAccessPointPreference preference =
+                (ConnectedAccessPointPreference)
+                        (mConnectedAccessPointPreferenceCategory.getPreference(0));
         // The AccessPoints need to be the same reference to ensure that updates are reflected
         // in the UI.
         if (preference.getAccessPoint() != connectedAp) {
@@ -829,8 +832,10 @@
 
         // Else same AP is connected, simply refresh the connected access point preference
         // (first and only access point in this category).
-        ((AccessPointPreference) mConnectedAccessPointPreferenceCategory.getPreference(0))
-                .refresh();
+        preference.refresh();
+        // Update any potential changes to the connected network and ensure that the callback is
+        // registered after an onStop lifecycle event.
+        registerCaptivePortalNetworkCallback(getCurrentWifiNetwork(), preference);
         return true;
     }
 
@@ -840,18 +845,19 @@
      */
     private void addConnectedAccessPointPreference(AccessPoint connectedAp) {
         final ConnectedAccessPointPreference pref =
-                createConnectedAccessPointPreference(
-                        connectedAp, this::isConnectedToCaptivePortalNetwork);
+                createConnectedAccessPointPreference(connectedAp);
+        registerCaptivePortalNetworkCallback(getCurrentWifiNetwork(), pref);
 
         // Launch details page or captive portal on click.
         pref.setOnPreferenceClickListener(
                 preference -> {
                     pref.getAccessPoint().saveWifiState(pref.getExtras());
-                    Network network = getConnectedWifiNetwork();
-                    if (isConnectedToCaptivePortalNetwork(network)) {
+                    if (mCaptivePortalNetworkCallback != null
+                            && mCaptivePortalNetworkCallback.isCaptivePortal()) {
                         ConnectivityManagerWrapper connectivityManagerWrapper =
                                 new ConnectivityManagerWrapper(mConnectivityManager);
-                        connectivityManagerWrapper.startCaptivePortalApp(network);
+                        connectivityManagerWrapper.startCaptivePortalApp(
+                                mCaptivePortalNetworkCallback.getNetwork());
                     } else {
                         launchNetworkDetailsFragment(pref);
                     }
@@ -874,6 +880,41 @@
         }
     }
 
+    private void registerCaptivePortalNetworkCallback(
+            Network wifiNetwork, ConnectedAccessPointPreference pref) {
+        if (wifiNetwork == null || pref == null) {
+            Log.w(TAG, "Network or Preference were null when registering callback.");
+            return;
+        }
+
+        if (mCaptivePortalNetworkCallback != null
+                && mCaptivePortalNetworkCallback.isSameNetworkAndPreference(wifiNetwork, pref)) {
+            return;
+        }
+
+        unregisterCaptivePortalNetworkCallback();
+
+        mCaptivePortalNetworkCallback = new CaptivePortalNetworkCallback(wifiNetwork, pref);
+        mConnectivityManager.registerNetworkCallback(
+                new NetworkRequest.Builder()
+                        .clearCapabilities()
+                        .addTransportType(TRANSPORT_WIFI)
+                        .build(),
+                mCaptivePortalNetworkCallback,
+                new Handler(Looper.getMainLooper()));
+    }
+
+    private void unregisterCaptivePortalNetworkCallback() {
+        if (mCaptivePortalNetworkCallback != null) {
+            try {
+                mConnectivityManager.unregisterNetworkCallback(mCaptivePortalNetworkCallback);
+            } catch (RuntimeException e) {
+                Log.e(TAG, "Unregistering CaptivePortalNetworkCallback failed.", e);
+            }
+            mCaptivePortalNetworkCallback = null;
+        }
+    }
+
     private void launchNetworkDetailsFragment(ConnectedAccessPointPreference pref) {
         new SubSettingLauncher(getContext())
                 .setTitle(getContext().getString(R.string.pref_title_network_details))
@@ -883,38 +924,15 @@
                 .launch();
     }
 
-    private boolean isConnectedToCaptivePortalNetwork() {
-        return isConnectedToCaptivePortalNetwork(getConnectedWifiNetwork());
-    }
-
-    private boolean isConnectedToCaptivePortalNetwork(Network network) {
-        if (mConnectivityManager == null || network == null) {
-            return false;
-        }
-        return WifiUtils.canSignIntoNetwork(mConnectivityManager.getNetworkCapabilities(network));
-    }
-
-    private Network getConnectedWifiNetwork() {
-        if (mConnectivityManager != null) {
-            Network networks[] = mConnectivityManager.getAllNetworks();
-            if (networks != null) {
-                for (Network network : networks) {
-                    NetworkCapabilities capabilities =
-                            mConnectivityManager.getNetworkCapabilities(network);
-                    if (capabilities != null
-                            && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
-                        return network;
-                    }
-                }
-            }
-        }
-        return null;
+    private Network getCurrentWifiNetwork() {
+        return mWifiManager != null ? mWifiManager.getCurrentNetwork() : null;
     }
 
     /** Removes all preferences and hide the {@link #mConnectedAccessPointPreferenceCategory}. */
     private void removeConnectedAccessPointPreference() {
         mConnectedAccessPointPreferenceCategory.removeAll();
         mConnectedAccessPointPreferenceCategory.setVisible(false);
+        unregisterCaptivePortalNetworkCallback();
     }
 
     private void setAdditionalSettingsSummaries() {
diff --git a/src/com/android/settings/wifi/WifiSummaryUpdater.java b/src/com/android/settings/wifi/WifiSummaryUpdater.java
index 2c56d08..81c531b 100644
--- a/src/com/android/settings/wifi/WifiSummaryUpdater.java
+++ b/src/com/android/settings/wifi/WifiSummaryUpdater.java
@@ -20,14 +20,17 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkScoreManager;
+import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
+
 import com.android.settings.R;
 import com.android.settings.widget.SummaryUpdater;
 import com.android.settingslib.wifi.WifiStatusTracker;
 
-import static android.net.wifi.WifiInfo.removeDoubleQuotes;
-
 /**
  * Helper class that listeners to wifi callback and notify client when there is update in
  * wifi summary info.
@@ -46,14 +49,18 @@
     }
 
     public WifiSummaryUpdater(Context context, OnSummaryChangeListener listener) {
-        this(context, listener, new WifiStatusTracker(context.getSystemService(WifiManager.class)));
+        this(context, listener, null);
     }
 
     @VisibleForTesting
     public WifiSummaryUpdater(Context context, OnSummaryChangeListener listener,
         WifiStatusTracker wifiTracker) {
         super(context, listener);
-        mWifiTracker = wifiTracker;
+        mWifiTracker = wifiTracker != null ? wifiTracker :
+                new WifiStatusTracker(context, context.getSystemService(WifiManager.class),
+                context.getSystemService(NetworkScoreManager.class),
+                context.getSystemService(ConnectivityManager.class),
+                        this::notifyChangeIfNeeded);
         mReceiver = new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
@@ -70,6 +77,7 @@
         } else {
             mContext.unregisterReceiver(mReceiver);
         }
+        mWifiTracker.setListening(register);
     }
 
     @Override
@@ -80,7 +88,12 @@
         if (!mWifiTracker.connected) {
             return mContext.getString(R.string.disconnected);
         }
-        return removeDoubleQuotes(mWifiTracker.ssid);
+        String ssid = WifiInfo.removeDoubleQuotes(mWifiTracker.ssid);
+        if (TextUtils.isEmpty(mWifiTracker.statusLabel)) {
+            return ssid;
+        }
+        return mContext.getResources().getString(
+                com.android.settingslib.R.string.preference_summary_default_combination,
+                ssid, mWifiTracker.statusLabel);
     }
-
 }
diff --git a/tests/robotests/src/com/android/settings/datetime/timezone/RegionSearchPickerTest.java b/tests/robotests/src/com/android/settings/datetime/timezone/RegionSearchPickerTest.java
index b2c7f03..8da9cbf 100644
--- a/tests/robotests/src/com/android/settings/datetime/timezone/RegionSearchPickerTest.java
+++ b/tests/robotests/src/com/android/settings/datetime/timezone/RegionSearchPickerTest.java
@@ -16,7 +16,20 @@
 
 package com.android.settings.datetime.timezone;
 
+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;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.widget.Filter;
+import android.widget.LinearLayout;
+
 import com.android.settings.datetime.timezone.BaseTimeZoneAdapter.AdapterItem;
+import com.android.settings.datetime.timezone.BaseTimeZoneAdapter.ItemViewHolder;
+import com.android.settings.datetime.timezone.RegionSearchPicker.RegionItem;
 import com.android.settings.datetime.timezone.model.TimeZoneData;
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
 
@@ -24,17 +37,23 @@
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
 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)
+@Config(shadows = {
+        RegionSearchPickerTest.ShadowBaseTimeZonePicker.class,
+        RegionSearchPickerTest.ShadowFragment.class,
+    }
+)
 public class RegionSearchPickerTest {
 
     @Test
@@ -44,16 +63,90 @@
         CountryZonesFinder finder = mock(CountryZonesFinder.class);
         when(finder.lookupAllCountryIsoCodes()).thenReturn(regionList);
 
-        RegionSearchPicker picker = new RegionSearchPicker() {
-            @Override
-            protected Locale getLocale() {
-                return Locale.US;
-            }
-        };
+        RegionSearchPicker picker = new RegionSearchPicker();
         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");
     }
+
+    // Test RegionSearchPicker does not crash due to the wrong assumption that no view is clicked
+    // before all views are updated and after internal data structure is updated for text filtering.
+    // This test mocks the text filtering event and emit click event immediately
+    // http://b/75322108
+    @Test
+    public void clickItemView_duringRegionSearch_shouldNotCrash() {
+        List regionList = new ArrayList();
+        regionList.add("US");
+        CountryZonesFinder finder = mock(CountryZonesFinder.class);
+        when(finder.lookupAllCountryIsoCodes()).thenReturn(regionList);
+
+        // Prepare the picker and adapter
+        RegionSearchPicker picker = new RegionSearchPicker();
+        BaseTimeZoneAdapter<RegionItem> adapter = picker.createAdapter(new TimeZoneData(finder));
+        // Prepare and bind a new ItemViewHolder with United States
+        ItemViewHolder viewHolder = adapter.onCreateViewHolder(
+                new LinearLayout(RuntimeEnvironment.application), 0);
+        adapter.onBindViewHolder(viewHolder, 0);
+        assertEquals(1, adapter.getItemCount());
+
+        // Pretend to search for a unknown region and no result is found.
+        FilterWrapper filterWrapper = new FilterWrapper(adapter.getFilter());
+        filterWrapper.publishEmptyResult("Unknown region 1");
+
+        // Assert that the adapter should have been updated with no item
+        assertEquals(0, adapter.getItemCount());
+        viewHolder.itemView.performClick(); // This should not crash
+    }
+
+    // FilterResults is a protected inner class. Use FilterWrapper to create an empty FilterResults
+    // instance.
+    private static class FilterWrapper extends Filter {
+
+        private final BaseTimeZoneAdapter.ArrayFilter mFilter;
+
+        private FilterWrapper(BaseTimeZoneAdapter.ArrayFilter filter) {
+            mFilter = filter;
+        }
+
+        @Override
+        protected FilterResults performFiltering(CharSequence charSequence) {
+            return null;
+        }
+
+        private void publishEmptyResult(CharSequence charSequence) {
+            FilterResults filterResults = new FilterResults();
+            filterResults.count = 0;
+            filterResults.values = new ArrayList<>();
+            publishResults(charSequence, filterResults);
+        }
+
+        @Override
+        protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
+            mFilter.publishResults(charSequence, filterResults);
+        }
+    }
+
+    // Robolectric can't start android.app.Fragment with support library v4 resources. Pretend
+    // the fragment has started, and provide the objects in context here.
+    @Implements(BaseTimeZonePicker.class)
+    public static class ShadowBaseTimeZonePicker extends ShadowFragment {
+
+        @Implementation
+        protected Locale getLocale() {
+            return Locale.US;
+        }
+    }
+
+    @Implements(Fragment.class)
+    public static class ShadowFragment {
+
+        private Activity mActivity = Robolectric.setupActivity(Activity.class);
+
+        @Implementation
+        public final Activity getActivity() {
+            return mActivity;
+        }
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryEntryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryEntryTest.java
index 21b1d29..b6be9ee 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryEntryTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryEntryTest.java
@@ -39,6 +39,8 @@
 import org.mockito.junit.MockitoRule;
 import org.robolectric.RobolectricTestRunner;
 
+import java.util.Locale;
+
 @RunWith(RobolectricTestRunner.class)
 public class BatteryEntryTest {
 
@@ -148,4 +150,17 @@
 
         assertThat(entry.extractPackagesFromSipper(entry.sipper)).isEqualTo(entry.sipper.mPackages);
     }
+
+    @Test
+    public void testUidCache_switchLocale_shouldCleanCache() {
+        BatteryEntry.stopRequestQueue();
+
+        Locale.setDefault(new Locale("en_US"));
+        BatteryEntry.sUidCache.put(Integer.toString(APP_UID), null);
+        assertThat(BatteryEntry.sUidCache).isNotEmpty();
+
+        Locale.setDefault(new Locale("zh_TW"));
+        createBatteryEntryForApp();
+        assertThat(BatteryEntry.sUidCache).isEmpty(); // check if cache is clear
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/wifi/ConnectedAccessPointPreferenceTest.java b/tests/robotests/src/com/android/settings/wifi/ConnectedAccessPointPreferenceTest.java
index b9b29d2..452fe03 100644
--- a/tests/robotests/src/com/android/settings/wifi/ConnectedAccessPointPreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/ConnectedAccessPointPreferenceTest.java
@@ -17,6 +17,7 @@
 package com.android.settings.wifi;
 
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -32,7 +33,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RuntimeEnvironment;
 
@@ -45,8 +45,6 @@
     private View mView;
     @Mock
     private ConnectedAccessPointPreference.OnGearClickListener mOnGearClickListener;
-    @Mock
-    private ConnectedAccessPointPreference.CaptivePortalStatus mCaptivePortalStatus;
     private Context mContext;
     private ConnectedAccessPointPreference mConnectedAccessPointPreference;
 
@@ -56,7 +54,7 @@
 
         mContext = RuntimeEnvironment.application;
         mConnectedAccessPointPreference = new ConnectedAccessPointPreference(mAccessPoint, mContext,
-                null, 0 /* iconResId */, false /* forSavedNetworks */, mCaptivePortalStatus);
+                null, 0 /* iconResId */, false /* forSavedNetworks */);
         mConnectedAccessPointPreference.setOnGearClickListener(mOnGearClickListener);
     }
 
@@ -78,15 +76,13 @@
 
     @Test
     public void testCaptivePortalStatus_isCaptivePortal_dividerDrawn() {
-        Mockito.when(mCaptivePortalStatus.isCaptivePortalNetwork()).thenReturn(true);
-        mConnectedAccessPointPreference.refresh();
+        mConnectedAccessPointPreference.setCaptivePortal(true);
         assertThat(mConnectedAccessPointPreference.shouldShowDivider()).isTrue();
     }
 
     @Test
     public void testCaptivePortalStatus_isNotCaptivePortal_dividerNotDrawn() {
-        Mockito.when(mCaptivePortalStatus.isCaptivePortalNetwork()).thenReturn(false);
-        mConnectedAccessPointPreference.refresh();
+        mConnectedAccessPointPreference.setCaptivePortal(false);
         assertThat(mConnectedAccessPointPreference.shouldShowDivider()).isFalse();
     }
 
diff --git a/tests/robotests/src/com/android/settings/wifi/WifiMasterSwitchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/WifiMasterSwitchPreferenceControllerTest.java
index f6acafd..69cfb10 100644
--- a/tests/robotests/src/com/android/settings/wifi/WifiMasterSwitchPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/WifiMasterSwitchPreferenceControllerTest.java
@@ -17,6 +17,7 @@
 package com.android.settings.wifi;
 
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -25,6 +26,7 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.IntentFilter;
+import android.net.NetworkScoreManager;
 import android.net.wifi.WifiManager;
 import android.support.v7.preference.Preference.OnPreferenceChangeListener;
 import android.support.v7.preference.PreferenceScreen;
@@ -51,6 +53,8 @@
     private PreferenceScreen mScreen;
     @Mock
     private MasterSwitchPreference mPreference;
+    @Mock
+    private NetworkScoreManager mNetworkScoreManager;
 
     private Context mContext;
     private WifiMasterSwitchPreferenceController mController;
@@ -61,6 +65,7 @@
         MockitoAnnotations.initMocks(this);
         mMetricsFeatureProvider = FakeFeatureFactory.setupForTest().getMetricsFeatureProvider();
         mContext = spy(RuntimeEnvironment.application.getApplicationContext());
+        when(mContext.getSystemService(NetworkScoreManager.class)).thenReturn(mNetworkScoreManager);
         mController = new WifiMasterSwitchPreferenceController(mContext, mMetricsFeatureProvider);
         when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
         when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifiManager);
diff --git a/tests/robotests/src/com/android/settings/wifi/WifiSummaryUpdaterTest.java b/tests/robotests/src/com/android/settings/wifi/WifiSummaryUpdaterTest.java
index fcfadda..f55c57f 100644
--- a/tests/robotests/src/com/android/settings/wifi/WifiSummaryUpdaterTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/WifiSummaryUpdaterTest.java
@@ -36,45 +36,41 @@
 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;
 
 @RunWith(SettingsRobolectricTestRunner.class)
 public class WifiSummaryUpdaterTest {
-
-    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
-    private WifiManager mWifiManager;
-    @Mock
-    private SummaryListener mListener;
+    @Mock private WifiStatusTracker mWifiTracker;
+    @Mock private SummaryListener mListener;
 
     private Context mContext;
     private WifiSummaryUpdater mSummaryUpdater;
-    private WifiStatusTracker mWifiTracker;
+
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mWifiTracker = new WifiStatusTracker(mWifiManager);
-
         mContext = spy(RuntimeEnvironment.application.getApplicationContext());
         mSummaryUpdater = new WifiSummaryUpdater(mContext, mListener, mWifiTracker);
     }
 
     @Test
-    public void register_true_shouldRegisterListener() {
+    public void register_true_shouldRegisterListenerAndTracker() {
         mSummaryUpdater.register(true);
 
         verify(mContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class));
+        verify(mWifiTracker).setListening(true);
     }
 
     @Test
-    public void register_false_shouldUnregisterListener() {
+    public void register_false_shouldUnregisterListenerAndTracker() {
         mSummaryUpdater.register(true);
         mSummaryUpdater.register(false);
 
         verify(mContext).unregisterReceiver(any(BroadcastReceiver.class));
+        verify(mWifiTracker).setListening(false);
     }
 
     @Test
@@ -82,7 +78,6 @@
         mSummaryUpdater.register(true);
         mContext.sendBroadcast(new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION));
 
-
         verify(mListener).onSummaryChanged(anyString());
     }
 
@@ -91,7 +86,6 @@
         mSummaryUpdater.register(true);
         mContext.sendBroadcast(new Intent(WifiManager.RSSI_CHANGED_ACTION));
 
-
         verify(mListener).onSummaryChanged(anyString());
     }
 
@@ -121,6 +115,16 @@
         assertThat(mSummaryUpdater.getSummary()).isEqualTo("Test Ssid");
     }
 
+    @Test
+    public void getSummary_wifiConnected_withSpeedLabel_shouldReturnSsid_withSpeedLabel() {
+        mWifiTracker.enabled = true;
+        mWifiTracker.connected = true;
+        mWifiTracker.ssid = "Test Ssid";
+        mWifiTracker.statusLabel = "Very Fast";
+
+        assertThat(mSummaryUpdater.getSummary()).isEqualTo("Test Ssid / Very Fast");
+    }
+
     private class SummaryListener implements OnSummaryChangeListener {
         String summary;