Merge "Remove wake from Settings gestures.xml"
diff --git a/res/color/circle_outline_color.xml b/res/color/circle_outline_color.xml
new file mode 100644
index 0000000..eb4e83c
--- /dev/null
+++ b/res/color/circle_outline_color.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:alpha="0.14"
+          android:color="?android:attr/colorForeground"/>
+</selector>
\ No newline at end of file
diff --git a/res/drawable/circle_outline.xml b/res/drawable/circle_outline.xml
new file mode 100644
index 0000000..1b2631d
--- /dev/null
+++ b/res/drawable/circle_outline.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="oval">
+    <stroke
+        android:width="1dp"
+        android:color="@color/circle_outline_color"/>
+</shape>
diff --git a/res/layout/advanced_bt_entity_sub.xml b/res/layout/advanced_bt_entity_sub.xml
index 07ea814..0f30583 100644
--- a/res/layout/advanced_bt_entity_sub.xml
+++ b/res/layout/advanced_bt_entity_sub.xml
@@ -26,9 +26,11 @@
         android:id="@+id/header_icon"
         android:layout_width="72dp"
         android:layout_height="72dp"
-        android:scaleType="fitCenter"
         android:layout_gravity="center_horizontal"
-        android:antialias="true"/>
+        android:antialias="true"
+        android:background="@drawable/circle_outline"
+        android:padding="8dp"
+        android:scaleType="fitCenter"/>
 
     <TextView
         android:id="@+id/header_title"
diff --git a/res/layout/time_zone_search_header.xml b/res/layout/time_zone_search_header.xml
new file mode 100644
index 0000000..5c4e0ee
--- /dev/null
+++ b/res/layout/time_zone_search_header.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 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.
+  -->
+<!-- similar to preference_material.xml but textview for emoji country flag
+instead of an ImageView -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+          android:id="@+android:id/title"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:layout_marginBottom="16dp"
+          android:layout_marginTop="16dp"
+          android:textAppearance="@style/TextAppearance.CategoryTitle"
+          android:textColor="?android:attr/colorAccent"
+          android:paddingStart="@dimen/preference_no_icon_padding_start"/>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e4d0d11..61b3366 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -7939,6 +7939,9 @@
     <!-- Sound & notification > Advanced section: Title for managing Do Not Disturb access option. [CHAR LIMIT=40] -->
     <string name="manage_zen_access_title">Do Not Disturb access</string>
 
+    <!-- Button title that grants 'Do Not Disturb' permission to an app [CHAR_LIMIT=60]-->
+    <string name="zen_access_detail_switch">Allow Do Not Disturb</string>
+
     <!-- Sound & notification > Do Not Disturb access > Text to display when the list is empty. [CHAR LIMIT=NONE] -->
     <string name="zen_access_empty_text">No installed apps have requested Do Not Disturb access</string>
 
@@ -10759,7 +10762,7 @@
     </plurals>
 
     <!-- Title for notification channel slice. [CHAR LIMIT=NONE] -->
-    <string name="manage_app_notification">Manage <xliff:g id="app_name" example="Settings">%1$s</xliff:g> Notifications</string>
+    <string name="manage_app_notification">Manage <xliff:g id="app_name" example="Settings">%1$s</xliff:g> notifications</string>
     <!-- Title for no suggested app in notification channel slice. [CHAR LIMIT=NONE] -->
     <string name="no_suggested_app">No suggested application</string>
     <!-- Summary for the channels count is equal or less than 3 in notification channel slice. [CHAR LIMIT=NONE] -->
@@ -10769,6 +10772,8 @@
     </plurals>
     <!-- Summary for the channels count is more than 3 in notification channel slice. [CHAR LIMIT=NONE] -->
     <string name="notification_many_channel_count_summary"><xliff:g id="notification_channel_count" example="4">%1$d</xliff:g> notification channels. Tap to manage all.</string>
+    <!-- Summary for recently installed app in contextual notification channel slice. [CHAR LIMIT=NONE] -->
+    <string name="recently_installed_app">You recently installed this app.</string>
 
     <!-- Title for the Switch output dialog (settings panel) with media related devices [CHAR LIMIT=50] -->
     <string name="media_output_panel_title">Switch output</string>
diff --git a/res/xml/zen_access_permission_details.xml b/res/xml/zen_access_permission_details.xml
new file mode 100644
index 0000000..afa8d80
--- /dev/null
+++ b/res/xml/zen_access_permission_details.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 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.
+  -->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:key="zen_access_permission_detail_settings"
+    android:title="@string/manage_zen_access_title">
+
+    <SwitchPreference
+        android:key="zen_access_switch"
+        android:title="@string/zen_access_detail_switch"/>
+
+</PreferenceScreen>
\ No newline at end of file
diff --git a/src/com/android/settings/applications/specialaccess/zenaccess/FriendlyWarningDialogFragment.java b/src/com/android/settings/applications/specialaccess/zenaccess/FriendlyWarningDialogFragment.java
new file mode 100644
index 0000000..fc85f7d
--- /dev/null
+++ b/src/com/android/settings/applications/specialaccess/zenaccess/FriendlyWarningDialogFragment.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2019 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.applications.specialaccess.zenaccess;
+
+import android.app.Dialog;
+import android.app.settings.SettingsEnums;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import androidx.appcompat.app.AlertDialog;
+
+import com.android.settings.R;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+
+/**
+ * Warning dialog when revoking zen access warning that zen rule instances will be deleted.
+ */
+public class FriendlyWarningDialogFragment extends InstrumentedDialogFragment {
+    static final String KEY_PKG = "p";
+    static final String KEY_LABEL = "l";
+
+
+    @Override
+    public int getMetricsCategory() {
+        return SettingsEnums.DIALOG_ZEN_ACCESS_REVOKE;
+    }
+
+    public FriendlyWarningDialogFragment setPkgInfo(String pkg, CharSequence label) {
+        Bundle args = new Bundle();
+        args.putString(KEY_PKG, pkg);
+        args.putString(KEY_LABEL, TextUtils.isEmpty(label) ? pkg : label.toString());
+        setArguments(args);
+        return this;
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final Bundle args = getArguments();
+        final String pkg = args.getString(KEY_PKG);
+        final String label = args.getString(KEY_LABEL);
+
+        final String title = getResources().getString(
+                R.string.zen_access_revoke_warning_dialog_title, label);
+        final String summary = getResources()
+                .getString(R.string.zen_access_revoke_warning_dialog_summary);
+        return new AlertDialog.Builder(getContext())
+                .setMessage(summary)
+                .setTitle(title)
+                .setCancelable(true)
+                .setPositiveButton(R.string.okay,
+                        (dialog, id) -> {
+                            ZenAccessController.deleteRules(getContext(), pkg);
+                            ZenAccessController.setAccess(getContext(), pkg, false);
+                        })
+                .setNegativeButton(R.string.cancel,
+                        (dialog, id) -> {
+                            // pass
+                        })
+                .create();
+    }
+}
diff --git a/src/com/android/settings/applications/specialaccess/zenaccess/ScaryWarningDialogFragment.java b/src/com/android/settings/applications/specialaccess/zenaccess/ScaryWarningDialogFragment.java
new file mode 100644
index 0000000..69318f8
--- /dev/null
+++ b/src/com/android/settings/applications/specialaccess/zenaccess/ScaryWarningDialogFragment.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2019 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.applications.specialaccess.zenaccess;
+
+import android.app.Dialog;
+import android.app.settings.SettingsEnums;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import androidx.appcompat.app.AlertDialog;
+
+import com.android.settings.R;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.notification.ZenAccessSettings;
+
+/**
+ * Warning dialog when allowing zen access warning about the privileges being granted.
+ */
+public class ScaryWarningDialogFragment extends InstrumentedDialogFragment {
+    static final String KEY_PKG = "p";
+    static final String KEY_LABEL = "l";
+
+    @Override
+    public int getMetricsCategory() {
+        return SettingsEnums.DIALOG_ZEN_ACCESS_GRANT;
+    }
+
+    public ScaryWarningDialogFragment setPkgInfo(String pkg, CharSequence label) {
+        Bundle args = new Bundle();
+        args.putString(KEY_PKG, pkg);
+        args.putString(KEY_LABEL, TextUtils.isEmpty(label) ? pkg : label.toString());
+        setArguments(args);
+        return this;
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final Bundle args = getArguments();
+        final String pkg = args.getString(KEY_PKG);
+        final String label = args.getString(KEY_LABEL);
+
+        final String title = getResources().getString(R.string.zen_access_warning_dialog_title,
+                label);
+        final String summary = getResources()
+                .getString(R.string.zen_access_warning_dialog_summary);
+        return new AlertDialog.Builder(getContext())
+                .setMessage(summary)
+                .setTitle(title)
+                .setCancelable(true)
+                .setPositiveButton(R.string.allow,
+                        (dialog, id) -> ZenAccessController.setAccess(getContext(), pkg, true))
+                .setNegativeButton(R.string.deny,
+                        (dialog, id) -> {
+                            // pass
+                        })
+                .create();
+    }
+}
diff --git a/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessController.java b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessController.java
index 88d444d..946599b 100644
--- a/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessController.java
+++ b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessController.java
@@ -17,12 +17,29 @@
 package com.android.settings.applications.specialaccess.zenaccess;
 
 import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.app.NotificationManager;
+import android.app.settings.SettingsEnums;
 import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.ParceledListSlice;
+import android.os.AsyncTask;
+import android.os.RemoteException;
+import android.util.ArraySet;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
 
 import com.android.settings.core.BasePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+
+import java.util.List;
+import java.util.Set;
 
 public class ZenAccessController extends BasePreferenceController {
 
+    private static final String TAG = "ZenAccessController";
+
     private final ActivityManager mActivityManager;
 
     public ZenAccessController(Context context, String preferenceKey) {
@@ -32,8 +49,68 @@
 
     @Override
     public int getAvailabilityStatus() {
-        return !mActivityManager.isLowRamDevice()
+        return isSupported(mActivityManager)
                 ? AVAILABLE_UNSEARCHABLE
                 : UNSUPPORTED_ON_DEVICE;
     }
+
+    public static boolean isSupported(ActivityManager activityManager) {
+        return !activityManager.isLowRamDevice();
+    }
+
+    public static Set<String> getPackagesRequestingNotificationPolicyAccess() {
+        final ArraySet<String> requestingPackages = new ArraySet<>();
+        try {
+            final String[] PERM = {
+                    android.Manifest.permission.ACCESS_NOTIFICATION_POLICY
+            };
+            final ParceledListSlice list = AppGlobals.getPackageManager()
+                    .getPackagesHoldingPermissions(PERM, 0 /*flags*/,
+                            ActivityManager.getCurrentUser());
+            final List<PackageInfo> pkgs = list.getList();
+            if (pkgs != null) {
+                for (PackageInfo info : pkgs) {
+                    requestingPackages.add(info.packageName);
+                }
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Cannot reach packagemanager", e);
+        }
+        return requestingPackages;
+    }
+
+    public static Set<String> getAutoApprovedPackages(Context context) {
+        final Set<String> autoApproved = new ArraySet<>();
+        autoApproved.addAll(context.getSystemService(NotificationManager.class)
+                .getEnabledNotificationListenerPackages());
+        return autoApproved;
+    }
+
+    public static boolean hasAccess(Context context, String pkg) {
+        return context.getSystemService(
+                NotificationManager.class).isNotificationPolicyAccessGrantedForPackage(pkg);
+    }
+
+    public static void setAccess(final Context context, final String pkg, final boolean access) {
+        logSpecialPermissionChange(access, pkg, context);
+        AsyncTask.execute(() -> {
+            final NotificationManager mgr = context.getSystemService(NotificationManager.class);
+            mgr.setNotificationPolicyAccessGranted(pkg, access);
+        });
+    }
+
+    public static void deleteRules(final Context context, final String pkg) {
+        AsyncTask.execute(() -> {
+            final NotificationManager mgr = context.getSystemService(NotificationManager.class);
+            mgr.removeAutomaticZenRules(pkg);
+        });
+    }
+
+    @VisibleForTesting
+    static void logSpecialPermissionChange(boolean enable, String packageName, Context context) {
+        int logCategory = enable ? SettingsEnums.APP_SPECIAL_PERMISSION_DND_ALLOW
+                : SettingsEnums.APP_SPECIAL_PERMISSION_DND_DENY;
+        FeatureFactory.getFactory(context).getMetricsFeatureProvider().action(context,
+                logCategory, packageName);
+    }
 }
diff --git a/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessDetails.java b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessDetails.java
new file mode 100644
index 0000000..a18e7d6
--- /dev/null
+++ b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessDetails.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2019 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.applications.specialaccess.zenaccess;
+
+import android.app.ActivityManager;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.preference.SwitchPreference;
+
+import com.android.settings.R;
+import com.android.settings.applications.AppInfoWithHeader;
+
+import java.util.Set;
+
+public class ZenAccessDetails extends AppInfoWithHeader implements
+        ZenAccessSettingObserverMixin.Listener {
+
+    private static final String SWITCH_PREF_KEY = "zen_access_switch";
+
+    @Override
+    public int getMetricsCategory() {
+        return SettingsEnums.ZEN_ACCESS_DETAIL;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        addPreferencesFromResource(R.xml.zen_access_permission_details);
+        getSettingsLifecycle().addObserver(
+                new ZenAccessSettingObserverMixin(getContext(), this /* listener */));
+    }
+
+    @Override
+    protected boolean refreshUi() {
+        final Context context = getContext();
+        if (!ZenAccessController.isSupported(context.getSystemService(ActivityManager.class))) {
+            return false;
+        }
+        // If this app didn't declare this permission in their manifest, don't bother showing UI.
+        final Set<String> needAccessApps =
+                ZenAccessController.getPackagesRequestingNotificationPolicyAccess();
+        if (!needAccessApps.contains(mPackageName)) {
+            return false;
+        }
+        updatePreference(context, findPreference(SWITCH_PREF_KEY));
+        return true;
+    }
+
+    @Override
+    protected AlertDialog createDialog(int id, int errorCode) {
+        return null;
+    }
+
+    public void updatePreference(Context context, SwitchPreference preference) {
+        final CharSequence label = mPackageInfo.applicationInfo.loadLabel(mPm);
+        final Set<String> autoApproved = ZenAccessController.getAutoApprovedPackages(context);
+        if (autoApproved.contains(mPackageName)) {
+            //Auto approved, user cannot do anything. Hard code summary and disable preference.
+            preference.setEnabled(false);
+            preference.setSummary(getString(R.string.zen_access_disabled_package_warning));
+            return;
+        }
+        preference.setChecked(ZenAccessController.hasAccess(context, mPackageName));
+        preference.setOnPreferenceChangeListener((p, newValue) -> {
+            final boolean access = (Boolean) newValue;
+            if (access) {
+                new ScaryWarningDialogFragment()
+                        .setPkgInfo(mPackageName, label)
+                        .show(getFragmentManager(), "dialog");
+            } else {
+                new FriendlyWarningDialogFragment()
+                        .setPkgInfo(mPackageName, label)
+                        .show(getFragmentManager(), "dialog");
+            }
+            return false;
+        });
+    }
+
+    @Override
+    public void onZenAccessPolicyChanged() {
+        refreshUi();
+    }
+}
diff --git a/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessSettingObserverMixin.java b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessSettingObserverMixin.java
new file mode 100644
index 0000000..30507ef
--- /dev/null
+++ b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessSettingObserverMixin.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2019 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.applications.specialaccess.zenaccess;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+
+public class ZenAccessSettingObserverMixin extends ContentObserver implements LifecycleObserver,
+        OnStart, OnStop {
+
+    public interface Listener {
+        void onZenAccessPolicyChanged();
+    }
+
+    private final Context mContext;
+    private final Listener mListener;
+
+    public ZenAccessSettingObserverMixin(Context context, Listener listener) {
+        super(new Handler(Looper.getMainLooper()));
+        mContext = context;
+        mListener = listener;
+    }
+
+    @Override
+    public void onChange(boolean selfChange, Uri uri) {
+        if (mListener != null) {
+            mListener.onZenAccessPolicyChanged();
+        }
+    }
+
+    @Override
+    public void onStart() {
+        if (!ZenAccessController.isSupported(mContext.getSystemService(ActivityManager.class))) {
+            return;
+        }
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(
+                        Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES),
+                false /* notifyForDescendants */,
+                this /* observer */);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS),
+                false /* notifyForDescendants */,
+                this /* observer */);
+    }
+
+    @Override
+    public void onStop() {
+        if (!ZenAccessController.isSupported(mContext.getSystemService(ActivityManager.class))) {
+            return;
+        }
+        mContext.getContentResolver().unregisterContentObserver(this /* observer */);
+    }
+}
diff --git a/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapter.java b/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapter.java
index ff980b2..66735c8 100644
--- a/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapter.java
+++ b/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapter.java
@@ -77,9 +77,10 @@
     @Override
     public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
         LayoutInflater inflater = LayoutInflater.from(parent.getContext());
-        switch(viewType) {
+        switch (viewType) {
             case TYPE_HEADER: {
-                final View view = inflater.inflate(R.layout.preference_category_material,
+                final View view = inflater.inflate(
+                        R.layout.time_zone_search_header,
                         parent, false);
                 return new HeaderViewHolder(view);
             }
@@ -136,7 +137,8 @@
         return mShowHeader && position == 0;
     }
 
-    public @NonNull ArrayFilter getFilter() {
+    @NonNull
+    public ArrayFilter getFilter() {
         if (mFilter == null) {
             mFilter = new ArrayFilter();
         }
@@ -153,14 +155,18 @@
 
     public interface AdapterItem {
         CharSequence getTitle();
+
         CharSequence getSummary();
+
         String getIconText();
+
         String getCurrentTime();
 
         /**
          * @return unique non-negative number
          */
         long getItemId();
+
         String[] getSearchKeys();
     }
 
diff --git a/src/com/android/settings/notification/ZenAccessSettings.java b/src/com/android/settings/notification/ZenAccessSettings.java
index d057c75..fca8255 100644
--- a/src/com/android/settings/notification/ZenAccessSettings.java
+++ b/src/com/android/settings/notification/ZenAccessSettings.java
@@ -18,56 +18,40 @@
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
-import android.app.AppGlobals;
-import android.app.Dialog;
 import android.app.NotificationManager;
 import android.app.settings.SettingsEnums;
 import android.content.Context;
-import android.content.DialogInterface;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ParceledListSlice;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.AsyncTask;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.RemoteException;
 import android.provider.SearchIndexableResource;
-import android.provider.Settings.Secure;
-import android.text.TextUtils;
 import android.util.ArraySet;
-import android.util.Log;
 import android.view.View;
 
-import androidx.annotation.VisibleForTesting;
-import androidx.appcompat.app.AlertDialog;
-import androidx.preference.Preference;
-import androidx.preference.Preference.OnPreferenceChangeListener;
 import androidx.preference.PreferenceScreen;
-import androidx.preference.SwitchPreference;
 
 import com.android.settings.R;
-import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
-import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.applications.AppInfoBase;
+import com.android.settings.applications.specialaccess.zenaccess.ZenAccessController;
+import com.android.settings.applications.specialaccess.zenaccess.ZenAccessDetails;
+import com.android.settings.applications.specialaccess.zenaccess.ZenAccessSettingObserverMixin;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settings.search.Indexable;
-import com.android.settings.widget.AppSwitchPreference;
 import com.android.settings.widget.EmptyTextSettings;
 import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.widget.apppreference.AppPreference;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 
 @SearchIndexable
-public class ZenAccessSettings extends EmptyTextSettings {
+public class ZenAccessSettings extends EmptyTextSettings implements
+        ZenAccessSettingObserverMixin.Listener {
     private final String TAG = "ZenAccessSettings";
 
-    private final SettingObserver mObserver = new SettingObserver();
     private Context mContext;
     private PackageManager mPkgMan;
     private NotificationManager mNoMan;
@@ -84,6 +68,8 @@
         mContext = getActivity();
         mPkgMan = mContext.getPackageManager();
         mNoMan = mContext.getSystemService(NotificationManager.class);
+        getSettingsLifecycle().addObserver(
+                new ZenAccessSettingObserverMixin(getContext(), this /* listener */));
     }
 
     @Override
@@ -102,30 +88,22 @@
         super.onResume();
         if (!ActivityManager.isLowRamDeviceStatic()) {
             reloadList();
-            getContentResolver().registerContentObserver(
-                    Secure.getUriFor(Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES), false,
-                    mObserver);
-            getContentResolver().registerContentObserver(
-                    Secure.getUriFor(Secure.ENABLED_NOTIFICATION_LISTENERS), false,
-                    mObserver);
         } else {
             setEmptyText(R.string.disabled_low_ram_device);
         }
     }
 
     @Override
-    public void onPause() {
-        super.onPause();
-        if (!ActivityManager.isLowRamDeviceStatic()) {
-            getContentResolver().unregisterContentObserver(mObserver);
-        }
+    public void onZenAccessPolicyChanged() {
+        reloadList();
     }
 
     private void reloadList() {
         final PreferenceScreen screen = getPreferenceScreen();
         screen.removeAll();
         final ArrayList<ApplicationInfo> apps = new ArrayList<>();
-        final ArraySet<String> requesting = getPackagesRequestingNotificationPolicyAccess();
+        final Set<String> requesting =
+                ZenAccessController.getPackagesRequestingNotificationPolicyAccess();
         if (!requesting.isEmpty()) {
             final List<ApplicationInfo> installed = mPkgMan.getInstalledApplications(0);
             if (installed != null) {
@@ -143,204 +121,42 @@
         for (ApplicationInfo app : apps) {
             final String pkg = app.packageName;
             final CharSequence label = app.loadLabel(mPkgMan);
-            final SwitchPreference pref = new AppSwitchPreference(getPrefContext());
+            final AppPreference pref = new AppPreference(getPrefContext());
             pref.setKey(pkg);
-            pref.setPersistent(false);
             pref.setIcon(app.loadIcon(mPkgMan));
             pref.setTitle(label);
-            pref.setChecked(hasAccess(pkg));
             if (autoApproved.contains(pkg)) {
+                //Auto approved, user cannot do anything. Hard code summary and disable preference.
                 pref.setEnabled(false);
                 pref.setSummary(getString(R.string.zen_access_disabled_package_warning));
+            } else {
+                // Not auto approved, update summary according to notification backend.
+                pref.setSummary(getPreferenceSummary(pkg));
             }
-            pref.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
-                @Override
-                public boolean onPreferenceChange(Preference preference, Object newValue) {
-                    final boolean access = (Boolean) newValue;
-                    if (access) {
-                        new ScaryWarningDialogFragment()
-                                .setPkgInfo(pkg, label)
-                                .show(getFragmentManager(), "dialog");
-                    } else {
-                        new FriendlyWarningDialogFragment()
-                                .setPkgInfo(pkg, label)
-                                .show(getFragmentManager(), "dialog");
-                    }
-                    return false;
-                }
+            pref.setOnPreferenceClickListener(preference -> {
+                AppInfoBase.startAppInfoFragment(
+                        ZenAccessDetails.class  /* fragment */,
+                        R.string.manage_zen_access_title /* titleRes */,
+                        pkg,
+                        app.uid,
+                        this /* source */,
+                        -1 /* requestCode */,
+                        getMetricsCategory() /* sourceMetricsCategory */);
+                return true;
             });
+
             screen.addPreference(pref);
         }
     }
 
-    private ArraySet<String> getPackagesRequestingNotificationPolicyAccess() {
-        ArraySet<String> requestingPackages = new ArraySet<>();
-        try {
-            final String[] PERM = {
-                    android.Manifest.permission.ACCESS_NOTIFICATION_POLICY
-            };
-            final ParceledListSlice list = AppGlobals.getPackageManager()
-                    .getPackagesHoldingPermissions(PERM, 0 /*flags*/,
-                            ActivityManager.getCurrentUser());
-            final List<PackageInfo> pkgs = list.getList();
-            if (pkgs != null) {
-                for (PackageInfo info : pkgs) {
-                    requestingPackages.add(info.packageName);
-                }
-            }
-        } catch(RemoteException e) {
-            Log.e(TAG, "Cannot reach packagemanager", e);
-        }
-        return requestingPackages;
-    }
-
-    private boolean hasAccess(String pkg) {
-        return mNoMan.isNotificationPolicyAccessGrantedForPackage(pkg);
-    }
-
-    private static void setAccess(final Context context, final String pkg, final boolean access) {
-        logSpecialPermissionChange(access, pkg, context);
-        AsyncTask.execute(new Runnable() {
-            @Override
-            public void run() {
-                final NotificationManager mgr = context.getSystemService(NotificationManager.class);
-                mgr.setNotificationPolicyAccessGranted(pkg, access);
-            }
-        });
-    }
-
-    @VisibleForTesting
-    static void logSpecialPermissionChange(boolean enable, String packageName, Context context) {
-        int logCategory = enable ? SettingsEnums.APP_SPECIAL_PERMISSION_DND_ALLOW
-                : SettingsEnums.APP_SPECIAL_PERMISSION_DND_DENY;
-        FeatureFactory.getFactory(context).getMetricsFeatureProvider().action(context,
-                logCategory, packageName);
-    }
-
-
-    private static void deleteRules(final Context context, final String pkg) {
-        AsyncTask.execute(new Runnable() {
-            @Override
-            public void run() {
-                final NotificationManager mgr = context.getSystemService(NotificationManager.class);
-                mgr.removeAutomaticZenRules(pkg);
-            }
-        });
-    }
-
-    private final class SettingObserver extends ContentObserver {
-        public SettingObserver() {
-            super(new Handler(Looper.getMainLooper()));
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            reloadList();
-        }
-    }
-
     /**
-     * Warning dialog when allowing zen access warning about the privileges being granted.
+     * @return the summary for the current state of whether the app associated with the given
+     * {@param packageName} is allowed to enter picture-in-picture.
      */
-    public static class ScaryWarningDialogFragment extends InstrumentedDialogFragment {
-        static final String KEY_PKG = "p";
-        static final String KEY_LABEL = "l";
-
-        @Override
-        public int getMetricsCategory() {
-            return SettingsEnums.DIALOG_ZEN_ACCESS_GRANT;
-        }
-
-        public ScaryWarningDialogFragment setPkgInfo(String pkg, CharSequence label) {
-            Bundle args = new Bundle();
-            args.putString(KEY_PKG, pkg);
-            args.putString(KEY_LABEL, TextUtils.isEmpty(label) ? pkg : label.toString());
-            setArguments(args);
-            return this;
-        }
-
-        @Override
-        public Dialog onCreateDialog(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            final Bundle args = getArguments();
-            final String pkg = args.getString(KEY_PKG);
-            final String label = args.getString(KEY_LABEL);
-
-            final String title = getResources().getString(R.string.zen_access_warning_dialog_title,
-                    label);
-            final String summary = getResources()
-                    .getString(R.string.zen_access_warning_dialog_summary);
-            return new AlertDialog.Builder(getContext())
-                    .setMessage(summary)
-                    .setTitle(title)
-                    .setCancelable(true)
-                    .setPositiveButton(R.string.allow,
-                            new DialogInterface.OnClickListener() {
-                                public void onClick(DialogInterface dialog, int id) {
-                                    setAccess(getContext(), pkg, true);
-                                }
-                            })
-                    .setNegativeButton(R.string.deny,
-                            new DialogInterface.OnClickListener() {
-                                public void onClick(DialogInterface dialog, int id) {
-                                    // pass
-                                }
-                            })
-                    .create();
-        }
-    }
-
-    /**
-     * Warning dialog when revoking zen access warning that zen rule instances will be deleted.
-     */
-    public static class FriendlyWarningDialogFragment extends InstrumentedDialogFragment {
-        static final String KEY_PKG = "p";
-        static final String KEY_LABEL = "l";
-
-
-        @Override
-        public int getMetricsCategory() {
-            return SettingsEnums.DIALOG_ZEN_ACCESS_REVOKE;
-        }
-
-        public FriendlyWarningDialogFragment setPkgInfo(String pkg, CharSequence label) {
-            Bundle args = new Bundle();
-            args.putString(KEY_PKG, pkg);
-            args.putString(KEY_LABEL, TextUtils.isEmpty(label) ? pkg : label.toString());
-            setArguments(args);
-            return this;
-        }
-
-        @Override
-        public Dialog onCreateDialog(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            final Bundle args = getArguments();
-            final String pkg = args.getString(KEY_PKG);
-            final String label = args.getString(KEY_LABEL);
-
-            final String title = getResources().getString(
-                    R.string.zen_access_revoke_warning_dialog_title, label);
-            final String summary = getResources()
-                    .getString(R.string.zen_access_revoke_warning_dialog_summary);
-            return new AlertDialog.Builder(getContext())
-                    .setMessage(summary)
-                    .setTitle(title)
-                    .setCancelable(true)
-                    .setPositiveButton(R.string.okay,
-                            new DialogInterface.OnClickListener() {
-                                public void onClick(DialogInterface dialog, int id) {
-                                    deleteRules(getContext(), pkg);
-                                    setAccess(getContext(), pkg, false);
-                                }
-                            })
-                    .setNegativeButton(R.string.cancel,
-                            new DialogInterface.OnClickListener() {
-                                public void onClick(DialogInterface dialog, int id) {
-                                    // pass
-                                }
-                            })
-                    .create();
-        }
+    private int getPreferenceSummary(String packageName) {
+        final boolean enabled = ZenAccessController.hasAccess(getContext(), packageName);
+        return enabled ? R.string.app_permission_summary_allowed
+                : R.string.app_permission_summary_not_allowed;
     }
 
     public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
diff --git a/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java b/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java
index 89565df..5588977 100644
--- a/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java
+++ b/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java
@@ -66,6 +66,7 @@
 import com.android.settings.wifi.WifiDialog.WifiDialogListener;
 import com.android.settings.wifi.WifiUtils;
 import com.android.settings.wifi.dpp.WifiDppUtils;
+import com.android.settings.wifi.savedaccesspoints.SavedAccessPointsWifiSettings;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -75,6 +76,8 @@
 import com.android.settingslib.widget.ActionButtonsPreference;
 import com.android.settingslib.widget.LayoutPreference;
 import com.android.settingslib.wifi.AccessPoint;
+import com.android.settingslib.wifi.WifiTracker;
+import com.android.settingslib.wifi.WifiTrackerFactory;
 
 import java.net.Inet4Address;
 import java.net.Inet6Address;
@@ -136,7 +139,9 @@
     private WifiConfiguration mWifiConfig;
     private WifiInfo mWifiInfo;
     private final WifiManager mWifiManager;
+    private final WifiTracker mWifiTracker;
     private final MetricsFeatureProvider mMetricsFeatureProvider;
+    private boolean mIsOutOfRange;
 
     // UI elements - in order of appearance
     private ActionButtonsPreference mButtonsPref;
@@ -176,7 +181,7 @@
                     // fall through
                 case WifiManager.NETWORK_STATE_CHANGED_ACTION:
                 case WifiManager.RSSI_CHANGED_ACTION:
-                    updateLiveNetworkInfo();
+                    updateNetworkInfo();
                     break;
             }
         }
@@ -206,14 +211,16 @@
         @Override
         public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
             // If the network just validated or lost Internet access, refresh network state.
-            // Don't do this on every NetworkCapabilities change because refreshNetworkState
-            // sends IPCs to the system server from the UI thread, which can cause jank.
+            // Don't do this on every NetworkCapabilities change because update accesspoint notify
+            // changed for accesspoint changed on the main thread, which can cause jank.
             if (network.equals(mNetwork) && !nc.equals(mNetworkCapabilities)) {
                 if (hasCapabilityChanged(nc, NET_CAPABILITY_VALIDATED) ||
                         hasCapabilityChanged(nc, NET_CAPABILITY_CAPTIVE_PORTAL)) {
-                    refreshNetworkState();
+                    mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo);
+                    refreshEntityHeader();
                 }
                 mNetworkCapabilities = nc;
+                refreshButtons();
                 updateIpLayerInfo();
             }
         }
@@ -226,6 +233,29 @@
         }
     };
 
+    private final WifiTracker.WifiListener mWifiListener = new WifiTracker.WifiListener() {
+        /** Called when the state of Wifi has changed. */
+        public void onWifiStateChanged(int state) {
+            Log.d(TAG, "onWifiStateChanged(" + state + ")");
+            // Do nothing.
+        }
+
+        /** Called when the connection state of wifi has changed. */
+        public void onConnectedChanged() {
+            Log.d(TAG, "onConnectedChanged");
+            // Do nothing.
+        }
+
+        /**
+         * Called to indicate the list of AccessPoints has been updated and
+         * {@link WifiTracker#getAccessPoints()} should be called to get the updated list.
+         */
+        public void onAccessPointsChanged() {
+            Log.d(TAG, "onAccessPointsChanged");
+            updateNetworkInfo();
+        }
+    };
+
     public static WifiDetailPreferenceController newInstance(
             AccessPoint accessPoint,
             ConnectivityManager connectivityManager,
@@ -270,6 +300,17 @@
 
         mLifecycle = lifecycle;
         lifecycle.addObserver(this);
+
+        if (SavedAccessPointsWifiSettings.usingDetailsFragment(mContext)) {
+            mWifiTracker = WifiTrackerFactory.create(
+                    mFragment.getActivity(),
+                    mWifiListener,
+                    mLifecycle,
+                    true /*includeSaved*/,
+                    true /*includeScans*/);
+        } else {
+            mWifiTracker = null;
+        }
     }
 
     @Override
@@ -360,7 +401,7 @@
         mNetwork = mWifiManager.getCurrentNetwork();
         mLinkProperties = mConnectivityManager.getLinkProperties(mNetwork);
         mNetworkCapabilities = mConnectivityManager.getNetworkCapabilities(mNetwork);
-        updateLiveNetworkInfo();
+        updateNetworkInfo();
         mContext.registerReceiver(mReceiver, mFilter);
         mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback,
                 mHandler);
@@ -377,72 +418,73 @@
         mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
     }
 
-    // TODO(b/124707751): Refactoring the code later, keeping it currently for stability.
-    protected void updateSavedNetworkInfo() {
-        mSignalStrengthPref.setVisible(false);
-        mFrequencyPref.setVisible(false);
-        mTxLinkSpeedPref.setVisible(false);
-        mRxLinkSpeedPref.setVisible(false);
-
-        // MAC Address Pref
-        mMacAddressPref.setSummary(mWifiConfig.getRandomizedMacAddress().toString());
-
-        refreshEntityHeader();
-
-        updateIpLayerInfo();
-
-        // Update whether the forget button should be displayed.
-        mButtonsPref.setButton1Visible(canForgetNetwork());
-    }
-
-    private void updateLiveNetworkInfo() {
-        // No need to fetch LinkProperties and NetworkCapabilities, they are updated by the
-        // callbacks. mNetwork doesn't change except in onResume.
-        mNetworkInfo = mConnectivityManager.getNetworkInfo(mNetwork);
-        mWifiInfo = mWifiManager.getConnectionInfo();
-        if (mNetwork == null || mNetworkInfo == null || mWifiInfo == null) {
-            exitActivity();
+    private void updateNetworkInfo() {
+        if(!updateAccessPoint()) {
             return;
         }
 
-        // Update whether the forget button should be displayed.
-        mButtonsPref.setButton1Visible(canForgetNetwork());
+        // refresh header
+        refreshEntityHeader();
 
-        refreshNetworkState();
+        // refresh Buttons
+        refreshButtons();
 
         // Update Connection Header icon and Signal Strength Preference
         refreshRssiViews();
-
-        // MAC Address Pref
-        mMacAddressPref.setSummary(mWifiInfo.getMacAddress());
-
-        // Transmit Link Speed Pref
-        int txLinkSpeedMbps = mWifiInfo.getTxLinkSpeedMbps();
-        mTxLinkSpeedPref.setVisible(txLinkSpeedMbps >= 0);
-        mTxLinkSpeedPref.setSummary(mContext.getString(
-                R.string.tx_link_speed, mWifiInfo.getTxLinkSpeedMbps()));
-
-        // Receive Link Speed Pref
-        int rxLinkSpeedMbps = mWifiInfo.getRxLinkSpeedMbps();
-        mRxLinkSpeedPref.setVisible(rxLinkSpeedMbps >= 0);
-        mRxLinkSpeedPref.setSummary(mContext.getString(
-                R.string.rx_link_speed, mWifiInfo.getRxLinkSpeedMbps()));
-
         // Frequency Pref
-        final int frequency = mWifiInfo.getFrequency();
-        String band = null;
-        if (frequency >= AccessPoint.LOWER_FREQ_24GHZ
-                && frequency < AccessPoint.HIGHER_FREQ_24GHZ) {
-            band = mContext.getResources().getString(R.string.wifi_band_24ghz);
-        } else if (frequency >= AccessPoint.LOWER_FREQ_5GHZ
-                && frequency < AccessPoint.HIGHER_FREQ_5GHZ) {
-            band = mContext.getResources().getString(R.string.wifi_band_5ghz);
-        } else {
-            Log.e(TAG, "Unexpected frequency " + frequency);
-        }
-        mFrequencyPref.setSummary(band);
-
+        refreshFrequency();
+        // Transmit Link Speed Pref
+        refreshTxSpeed();
+        // Receive Link Speed Pref
+        refreshRxSpeed();
+        // IP related information
         updateIpLayerInfo();
+        // MAC Address Pref
+        refreshMacAddress();
+
+    }
+
+    private boolean updateAccessPoint() {
+        boolean changed = false;
+        if (mWifiTracker != null) {
+            updateAccessPointFromScannedList();
+            // refresh UI if signal level changed for disconnect network.
+            changed = mRssiSignalLevel != mAccessPoint.getLevel();
+        }
+
+        if (mAccessPoint.isActive()) {
+            // No need to fetch LinkProperties and NetworkCapabilities, they are updated by the
+            // callbacks. mNetwork doesn't change except in onResume.
+            mNetworkInfo = mConnectivityManager.getNetworkInfo(mNetwork);
+            mWifiInfo = mWifiManager.getConnectionInfo();
+            if (mNetwork == null || mNetworkInfo == null || mWifiInfo == null) {
+                exitActivity();
+                return false;
+            }
+
+            changed |= mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo);
+            // If feature for saved network not enabled, always return true.
+            return mWifiTracker == null || changed;
+        }
+
+        return changed;
+    }
+
+    private void updateAccessPointFromScannedList() {
+        mIsOutOfRange = true;
+
+        if (mAccessPoint.getConfig() == null) {
+            return;
+        }
+
+        for (AccessPoint ap : mWifiTracker.getAccessPoints()) {
+            if (ap.getConfig() != null
+                    && mAccessPoint.matches(ap.getConfig())) {
+                mAccessPoint = ap;
+                mIsOutOfRange = false;
+                return;
+            }
+        }
     }
 
     private void exitActivity() {
@@ -452,14 +494,16 @@
         mFragment.getActivity().finish();
     }
 
-    private void refreshNetworkState() {
-        mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo);
-        refreshEntityHeader();
-    }
-
     private void refreshRssiViews() {
         int signalLevel = mAccessPoint.getLevel();
 
+        // Disappears signal view if not in range. e.g. for saved networks.
+        if (mIsOutOfRange) {
+            mSignalStrengthPref.setVisible(false);
+            mRssiSignalLevel = -1;
+            return;
+        }
+
         if (mRssiSignalLevel == signalLevel) {
             return;
         }
@@ -477,6 +521,84 @@
         mSignalStrengthPref.setIcon(wifiIconDark);
 
         mSignalStrengthPref.setSummary(mSignalStr[mRssiSignalLevel]);
+        mSignalStrengthPref.setVisible(true);
+    }
+
+    private void refreshFrequency() {
+        if (mWifiInfo == null) {
+            mFrequencyPref.setVisible(false);
+            return;
+        }
+
+        final int frequency = mWifiInfo.getFrequency();
+        String band = null;
+        if (frequency >= AccessPoint.LOWER_FREQ_24GHZ
+                && frequency < AccessPoint.HIGHER_FREQ_24GHZ) {
+            band = mContext.getResources().getString(R.string.wifi_band_24ghz);
+        } else if (frequency >= AccessPoint.LOWER_FREQ_5GHZ
+                && frequency < AccessPoint.HIGHER_FREQ_5GHZ) {
+            band = mContext.getResources().getString(R.string.wifi_band_5ghz);
+        } else {
+            Log.e(TAG, "Unexpected frequency " + frequency);
+        }
+        mFrequencyPref.setSummary(band);
+        mFrequencyPref.setVisible(true);
+    }
+
+    private void refreshTxSpeed() {
+        if (mWifiInfo == null) {
+            mTxLinkSpeedPref.setVisible(false);
+            return;
+        }
+
+        int txLinkSpeedMbps = mWifiInfo.getTxLinkSpeedMbps();
+        mTxLinkSpeedPref.setVisible(txLinkSpeedMbps >= 0);
+        mTxLinkSpeedPref.setSummary(mContext.getString(
+                R.string.tx_link_speed, mWifiInfo.getTxLinkSpeedMbps()));
+    }
+
+    private void refreshRxSpeed() {
+        if (mWifiInfo == null) {
+            mRxLinkSpeedPref.setVisible(false);
+            return;
+        }
+
+        int rxLinkSpeedMbps = mWifiInfo.getRxLinkSpeedMbps();
+        mRxLinkSpeedPref.setVisible(rxLinkSpeedMbps >= 0);
+        mRxLinkSpeedPref.setSummary(mContext.getString(
+                R.string.rx_link_speed, mWifiInfo.getRxLinkSpeedMbps()));
+    }
+
+    private void refreshMacAddress() {
+        String macAddress = getMacAddress();
+        if (macAddress == null) {
+            mMacAddressPref.setVisible(false);
+            return;
+        }
+
+        mMacAddressPref.setVisible(true);
+        mMacAddressPref.setSummary(macAddress);
+    }
+
+    private String getMacAddress() {
+        if (mWifiInfo != null) {
+            // get MAC address from connected network information
+            return mWifiInfo.getMacAddress();
+        }
+
+        // return randomized MAC address
+        if (mWifiConfig.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_PERSISTENT) {
+            return mWifiConfig.getRandomizedMacAddress().toString();
+        }
+
+        // return device MAC address
+        final String[] macAddresses = mWifiManager.getFactoryMacAddresses();
+        if (macAddresses != null && macAddresses.length > 0) {
+            return macAddresses[0];
+        }
+
+        Log.e(TAG, "Can't get device MAC address!");
+        return null;
     }
 
     private void updatePreference(Preference pref, String detailText) {
@@ -488,13 +610,17 @@
         }
     }
 
-    private void updateIpLayerInfo() {
+    private void refreshButtons() {
+        mButtonsPref.setButton1Visible(canForgetNetwork());
         mButtonsPref.setButton2Visible(canSignIntoNetwork());
         mButtonsPref.setButton3Visible(canShareNetwork());
         mButtonsPref.setVisible(
                 canSignIntoNetwork() || canForgetNetwork() || canShareNetwork());
+    }
 
-        if (mNetwork == null || mLinkProperties == null) {
+    private void updateIpLayerInfo() {
+        // Hide IP layer info if not a connected network.
+        if (!mAccessPoint.isActive() || mNetwork == null || mLinkProperties == null) {
             mIpAddressPref.setVisible(false);
             mSubnetPref.setVisible(false);
             mGatewayPref.setVisible(false);
diff --git a/src/com/android/settings/wifi/details/WifiDetailSavedNetworkPreferenceController.java b/src/com/android/settings/wifi/details/WifiDetailSavedNetworkPreferenceController.java
deleted file mode 100644
index 3407890..0000000
--- a/src/com/android/settings/wifi/details/WifiDetailSavedNetworkPreferenceController.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2019 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.details;
-
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.wifi.WifiManager;
-import android.os.Handler;
-
-import androidx.fragment.app.Fragment;
-
-import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
-import com.android.settingslib.core.lifecycle.Lifecycle;
-import com.android.settingslib.wifi.AccessPoint;
-
-public class WifiDetailSavedNetworkPreferenceController extends WifiDetailPreferenceController {
-
-    WifiDetailSavedNetworkPreferenceController(AccessPoint accessPoint,
-            ConnectivityManager connectivityManager, Context context,
-            Fragment fragment, Handler handler,
-            Lifecycle lifecycle,
-            WifiManager wifiManager,
-            MetricsFeatureProvider metricsFeatureProvider,
-            IconInjector injector) {
-        super(accessPoint, connectivityManager, context, fragment, handler, lifecycle, wifiManager,
-                metricsFeatureProvider, injector);
-    }
-
-    public static WifiDetailSavedNetworkPreferenceController newInstance(
-            AccessPoint accessPoint,
-            ConnectivityManager connectivityManager,
-            Context context,
-            Fragment fragment,
-            Handler handler,
-            Lifecycle lifecycle,
-            WifiManager wifiManager,
-            MetricsFeatureProvider metricsFeatureProvider) {
-        return new WifiDetailSavedNetworkPreferenceController(
-                accessPoint, connectivityManager, context, fragment, handler, lifecycle,
-                wifiManager, metricsFeatureProvider, new IconInjector(context));
-    }
-
-    @Override
-    public void onPause() {
-        // Do nothing
-    }
-
-    @Override
-    public void onResume() {
-        updateSavedNetworkInfo();
-    }
-}
diff --git a/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java b/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java
index 7edd227..66587ed 100644
--- a/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java
+++ b/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java
@@ -51,9 +51,6 @@
 
     private static final String TAG = "WifiNetworkDetailsFrg";
 
-    // Extra for if current fragment shows saved network status or not.
-    public static final String EXTRA_IS_SAVED_NETWORK = "SavedNetwork";
-
     private AccessPoint mAccessPoint;
     private WifiDetailPreferenceController mWifiDetailPreferenceController;
 
@@ -126,30 +123,15 @@
         final List<AbstractPreferenceController> controllers = new ArrayList<>();
         final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
 
-        final boolean isDisplaySavedNetworkDetails =
-                getArguments().getBoolean(EXTRA_IS_SAVED_NETWORK, false /* defaultValue */);
-        if (isDisplaySavedNetworkDetails) {
-            mWifiDetailPreferenceController =
-                    WifiDetailSavedNetworkPreferenceController.newInstance(
-                            mAccessPoint,
-                            cm,
-                            context,
-                            this,
-                            new Handler(Looper.getMainLooper()),  // UI thread.
-                            getSettingsLifecycle(),
-                            context.getSystemService(WifiManager.class),
-                            mMetricsFeatureProvider);
-        } else {
-            mWifiDetailPreferenceController = WifiDetailPreferenceController.newInstance(
-                    mAccessPoint,
-                    cm,
-                    context,
-                    this,
-                    new Handler(Looper.getMainLooper()),  // UI thread.
-                    getSettingsLifecycle(),
-                    context.getSystemService(WifiManager.class),
-                    mMetricsFeatureProvider);
-        }
+        mWifiDetailPreferenceController = WifiDetailPreferenceController.newInstance(
+                mAccessPoint,
+                cm,
+                context,
+                this,
+                new Handler(Looper.getMainLooper()),  // UI thread.
+                getSettingsLifecycle(),
+                context.getSystemService(WifiManager.class),
+                mMetricsFeatureProvider);
 
         controllers.add(mWifiDetailPreferenceController);
         controllers.add(new WifiMeteredPreferenceController(context, mAccessPoint.getConfig()));
diff --git a/src/com/android/settings/wifi/savedaccesspoints/SavedAccessPointsWifiSettings.java b/src/com/android/settings/wifi/savedaccesspoints/SavedAccessPointsWifiSettings.java
index ea858f3..3f600e6 100644
--- a/src/com/android/settings/wifi/savedaccesspoints/SavedAccessPointsWifiSettings.java
+++ b/src/com/android/settings/wifi/savedaccesspoints/SavedAccessPointsWifiSettings.java
@@ -108,7 +108,6 @@
             }
             final Bundle savedState = new Bundle();
             mSelectedAccessPoint.saveWifiState(savedState);
-            savedState.putBoolean(WifiNetworkDetailsFragment.EXTRA_IS_SAVED_NETWORK, true);
 
             new SubSettingLauncher(getContext())
                     .setTitleText(mSelectedAccessPoint.getTitle())
diff --git a/tests/robotests/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessControllerTest.java b/tests/robotests/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessControllerTest.java
index bcb4bb3..6041e9d 100644
--- a/tests/robotests/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessControllerTest.java
@@ -18,26 +18,41 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.app.NotificationManager;
 import android.content.Context;
 
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.shadow.ShadowNotificationManager;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
 import org.robolectric.shadow.api.Shadow;
 import org.robolectric.shadows.ShadowActivityManager;
 
 @RunWith(RobolectricTestRunner.class)
 public class ZenAccessControllerTest {
 
+    private static final String TEST_PKG = "com.test.package";
+
+    private FakeFeatureFactory mFeatureFactory;
     private Context mContext;
     private ZenAccessController mController;
     private ShadowActivityManager mActivityManager;
 
+
     @Before
     public void setUp() {
         mContext = RuntimeEnvironment.application;
+        mFeatureFactory = FakeFeatureFactory.setupForTest();
         mController = new ZenAccessController(mContext, "key");
         mActivityManager = Shadow.extract(mContext.getSystemService(Context.ACTIVITY_SERVICE));
     }
@@ -52,4 +67,32 @@
         mActivityManager.setIsLowRamDevice(true);
         assertThat(mController.isAvailable()).isFalse();
     }
+
+    @Test
+    public void logSpecialPermissionChange() {
+        ZenAccessController.logSpecialPermissionChange(true, "app", mContext);
+        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+                eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_DND_ALLOW),
+                eq("app"));
+
+        ZenAccessController.logSpecialPermissionChange(false, "app", mContext);
+        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+                eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_DND_DENY),
+                eq("app"));
+    }
+
+    @Test
+    @Config(shadows = ShadowNotificationManager.class)
+    public void hasAccess_granted_yes() {
+        final ShadowNotificationManager snm = Shadow.extract(mContext.getSystemService(
+                NotificationManager.class));
+        snm.setNotificationPolicyAccessGrantedForPackage(TEST_PKG);
+        assertThat(ZenAccessController.hasAccess(mContext, TEST_PKG)).isTrue();
+    }
+
+    @Test
+    @Config(shadows = ShadowNotificationManager.class)
+    public void hasAccess_notGranted_no() {
+        assertThat(ZenAccessController.hasAccess(mContext, TEST_PKG)).isFalse();
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessSettingObserverMixinTest.java b/tests/robotests/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessSettingObserverMixinTest.java
new file mode 100644
index 0000000..cba1a51
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessSettingObserverMixinTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2019 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.applications.specialaccess.zenaccess;
+
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.lifecycle.LifecycleOwner;
+
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowActivityManager;
+
+@RunWith(RobolectricTestRunner.class)
+public class ZenAccessSettingObserverMixinTest {
+
+    @Mock
+    private ZenAccessSettingObserverMixin.Listener mListener;
+
+    private Context mContext;
+    private LifecycleOwner mLifecycleOwner;
+    private Lifecycle mLifecycle;
+    private ZenAccessSettingObserverMixin mMixin;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(RuntimeEnvironment.application);
+        mLifecycleOwner = () -> mLifecycle;
+        mLifecycle = new Lifecycle(mLifecycleOwner);
+
+        mMixin = new ZenAccessSettingObserverMixin(mContext, mListener);
+
+        mLifecycle.addObserver(mMixin);
+    }
+
+    @Test
+    public void onStart_lowMemory_shouldNotRegisterListener() {
+        final ShadowActivityManager sam = Shadow.extract(
+                mContext.getSystemService(ActivityManager.class));
+        sam.setIsLowRamDevice(true);
+
+        mLifecycle.handleLifecycleEvent(ON_START);
+
+        mContext.getContentResolver().notifyChange(Settings.Secure.getUriFor(
+                Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES), null);
+
+        verify(mListener, never()).onZenAccessPolicyChanged();
+    }
+
+    @Test
+    public void onStart_highMemory_shouldRegisterListener() {
+        final ShadowActivityManager sam = Shadow.extract(
+                mContext.getSystemService(ActivityManager.class));
+        sam.setIsLowRamDevice(false);
+
+        mLifecycle.handleLifecycleEvent(ON_START);
+
+        mContext.getContentResolver().notifyChange(Settings.Secure.getUriFor(
+                Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES), null);
+
+        verify(mListener).onZenAccessPolicyChanged();
+    }
+
+    @Test
+    public void onStop_shouldUnregisterListener() {
+        final ShadowActivityManager sam = Shadow.extract(
+                mContext.getSystemService(ActivityManager.class));
+        sam.setIsLowRamDevice(false);
+
+        mLifecycle.handleLifecycleEvent(ON_START);
+        mLifecycle.handleLifecycleEvent(ON_STOP);
+
+        mContext.getContentResolver().notifyChange(Settings.Secure.getUriFor(
+                Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES), null);
+
+        verify(mListener, never()).onZenAccessPolicyChanged();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/ZenAccessSettingsTest.java b/tests/robotests/src/com/android/settings/notification/ZenAccessSettingsTest.java
deleted file mode 100644
index c2a6f4f..0000000
--- a/tests/robotests/src/com/android/settings/notification/ZenAccessSettingsTest.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.settings.notification;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-
-import com.android.internal.logging.nano.MetricsProto;
-import com.android.settings.testutils.FakeFeatureFactory;
-
-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.RobolectricTestRunner;
-
-@RunWith(RobolectricTestRunner.class)
-public class ZenAccessSettingsTest {
-
-    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
-    private Context mContext;
-
-    private FakeFeatureFactory mFeatureFactory;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        mFeatureFactory = FakeFeatureFactory.setupForTest();
-    }
-
-    @Test
-    public void logSpecialPermissionChange() {
-        ZenAccessSettings.logSpecialPermissionChange(true, "app", mContext);
-        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
-                eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_DND_ALLOW),
-                eq("app"));
-
-        ZenAccessSettings.logSpecialPermissionChange(false, "app", mContext);
-        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
-                eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_DND_DENY),
-                eq("app"));
-    }
-}
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowNotificationManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowNotificationManager.java
index 8325777..78fb23f 100644
--- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowNotificationManager.java
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowNotificationManager.java
@@ -19,15 +19,19 @@
 import android.app.NotificationManager;
 import android.net.Uri;
 import android.service.notification.ZenModeConfig;
+import android.util.ArraySet;
 
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
 
+import java.util.Set;
+
 @Implements(NotificationManager.class)
 public class ShadowNotificationManager {
 
     private int mZenMode;
     private ZenModeConfig mZenModeConfig;
+    private Set<String> mNotificationPolicyGrantedPackages = new ArraySet<>();
 
     @Implementation
     protected void setZenMode(int mode, Uri conditionId, String reason) {
@@ -40,6 +44,11 @@
     }
 
     @Implementation
+    protected boolean isNotificationPolicyAccessGrantedForPackage(String pkg) {
+        return mNotificationPolicyGrantedPackages.contains(pkg);
+    }
+
+    @Implementation
     public ZenModeConfig getZenModeConfig() {
         return mZenModeConfig;
     }
@@ -47,4 +56,8 @@
     public void setZenModeConfig(ZenModeConfig config) {
         mZenModeConfig = config;
     }
+
+    public void setNotificationPolicyAccessGrantedForPackage(String pkg) {
+        mNotificationPolicyGrantedPackages.add(pkg);
+    }
 }