diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4bb1a8c530a..5b786813069 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2389,6 +2389,19 @@
android:value="com.android.settings.applications.specialaccess.pictureinpicture.PictureInPictureDetails" />
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 00000000000..1b2631d522d
--- /dev/null
+++ b/res/drawable/circle_outline.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
diff --git a/res/drawable/ic_gray_scale_24dp.xml b/res/drawable/ic_gray_scale_24dp.xml
new file mode 100644
index 00000000000..3fda134ade7
--- /dev/null
+++ b/res/drawable/ic_gray_scale_24dp.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/res/layout/advanced_bt_entity_sub.xml b/res/layout/advanced_bt_entity_sub.xml
index 07ea8146fad..0f305830bdf 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"/>
+
+
+
+
diff --git a/res/values/config.xml b/res/values/config.xml
index e6ada66a442..805469a63e5 100755
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -356,4 +356,7 @@
content://com.google.android.gms.nearby.fastpair/device_status_list_item
+
+
+
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index f5b6e958789..ef6fc4f567a 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -392,7 +392,7 @@
4dp
16dp
16dp
- 44dp
+ 48dp
10dp
10dp
24dp
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e4d0d115f0d..631d9120cad 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -4525,8 +4525,8 @@
Keyboard shortcuts helper
Display available shortcuts
-
- Work profile input & assistance
+
+ Work profile keyboards & tools
Virtual keyboard for work
@@ -7939,6 +7939,9 @@
Do Not Disturb access
+
+ Allow Do Not Disturb
+
No installed apps have requested Do Not Disturb access
@@ -9325,6 +9328,12 @@
Screen tinted amber
+
+ Greyscale
+
+
+ Display only in grey color
+
%1$d
@@ -10759,7 +10768,7 @@
- Manage %1$s Notifications
+ Manage %1$s notifications
No suggested application
@@ -10769,6 +10778,8 @@
%1$d notification channels. Tap to manage all.
+
+ You recently installed this app.
Switch output
diff --git a/res/xml/gestures.xml b/res/xml/gestures.xml
index f7056307826..8515bd71892 100644
--- a/res/xml/gestures.xml
+++ b/res/xml/gestures.xml
@@ -27,12 +27,6 @@
android:fragment="com.android.settings.gestures.AssistGestureSettings"
settings:controller="com.android.settings.gestures.AssistGestureSettingsPreferenceController" />
-
-
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 97ff613a317..ead0d6a3e21 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -96,6 +96,7 @@ public class Settings extends SettingsActivity {
public static class PictureInPictureSettingsActivity extends SettingsActivity { /* empty */ }
public static class AppPictureInPictureSettingsActivity extends SettingsActivity { /* empty */ }
public static class ZenAccessSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class ZenAccessDetailSettingsActivity extends SettingsActivity {}
public static class ConditionProviderSettingsActivity extends SettingsActivity { /* empty */ }
public static class UsbSettingsActivity extends SettingsActivity { /* empty */ }
public static class UsbDetailsActivity extends SettingsActivity { /* empty */ }
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 00000000000..fc85f7dffd0
--- /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 00000000000..69318f8d6af
--- /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 88d444d485c..946599b4aa8 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 @@ public class ZenAccessController extends BasePreferenceController {
@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 getPackagesRequestingNotificationPolicyAccess() {
+ final ArraySet 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 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 getAutoApprovedPackages(Context context) {
+ final Set 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 00000000000..a18e7d63cad
--- /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 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 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 00000000000..30507efffa9
--- /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/backup/OWNERS b/src/com/android/settings/backup/OWNERS
index c026a350e75..a7b55fd134b 100644
--- a/src/com/android/settings/backup/OWNERS
+++ b/src/com/android/settings/backup/OWNERS
@@ -1,6 +1,13 @@
-# Default reviewers for this and subdirectories.
+# Use this reviewer by default.
+br-framework-team+reviews@google.com
+
+# People who can approve changes for submission.
+anniemeng@google.com
+nathch@google.com
+rthakohov@google.com
+
+# Others (in case above are not available).
bryanmawhinney@google.com
-cprins@google.com
jorlow@google.com
philippov@google.com
stefanot@google.com
\ No newline at end of file
diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java
index ba64a80f20c..d522918fa6d 100644
--- a/src/com/android/settings/core/gateway/SettingsGateway.java
+++ b/src/com/android/settings/core/gateway/SettingsGateway.java
@@ -50,6 +50,7 @@ import com.android.settings.applications.specialaccess.deviceadmin.DeviceAdminSe
import com.android.settings.applications.specialaccess.pictureinpicture.PictureInPictureDetails;
import com.android.settings.applications.specialaccess.pictureinpicture.PictureInPictureSettings;
import com.android.settings.applications.specialaccess.vrlistener.VrListenerSettings;
+import com.android.settings.applications.specialaccess.zenaccess.ZenAccessDetails;
import com.android.settings.backup.PrivacySettings;
import com.android.settings.backup.ToggleBackupSettingFragment;
import com.android.settings.backup.UserBackupSettingsActivity;
@@ -209,6 +210,7 @@ public class SettingsGateway {
UserSettings.class.getName(),
NotificationAccessSettings.class.getName(),
ZenAccessSettings.class.getName(),
+ ZenAccessDetails.class.getName(),
ZenModeAutomationSettings.class.getName(),
PrintSettingsFragment.class.getName(),
PrintJobSettingsFragment.class.getName(),
diff --git a/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapter.java b/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapter.java
index ff980b2ceca..66735c8a5e1 100644
--- a/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapter.java
+++ b/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapter.java
@@ -77,9 +77,10 @@ public class BaseTimeZoneAdapter
@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 @@ public class BaseTimeZoneAdapter
return mShowHeader && position == 0;
}
- public @NonNull ArrayFilter getFilter() {
+ @NonNull
+ public ArrayFilter getFilter() {
if (mFilter == null) {
mFilter = new ArrayFilter();
}
@@ -153,14 +155,18 @@ public class BaseTimeZoneAdapter
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/homepage/contextualcards/ContextualCardLoader.java b/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java
index ff2ee91468e..1f5f7dd5694 100644
--- a/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java
+++ b/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java
@@ -187,7 +187,6 @@ public class ContextualCardLoader extends AsyncLoaderCompat
// Collect future and eligible cards
for (Future cardFuture : eligibleCards) {
try {
- //TODO(b/124492762): Log latency and timeout occurrence.
final ContextualCard card = cardFuture.get(ELIGIBILITY_CHECKER_TIMEOUT_MS,
TimeUnit.MILLISECONDS);
if (card != null) {
diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java b/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java
index 8f7e84acd2f..7df322d5211 100644
--- a/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java
+++ b/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java
@@ -22,6 +22,7 @@ import static com.android.settings.intelligence.ContextualCardProto.ContextualCa
import static java.util.stream.Collectors.groupingBy;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import android.provider.Settings;
@@ -38,6 +39,7 @@ import androidx.loader.content.Loader;
import com.android.settings.homepage.contextualcards.slices.SliceContextualCardRenderer;
import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
@@ -195,8 +197,8 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo
@Override
public void onFinishCardLoading(List cards) {
final long loadTime = System.currentTimeMillis() - mStartTime;
- //TODO(b/123668403): remove the log here once we do the change with FutureTask
Log.d(TAG, "Total loading time = " + loadTime);
+
final List cardsToKeep = getCardsToKeep(cards);
//navigate back to the homepage, screen rotate or after card dismissal
@@ -206,15 +208,25 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo
return;
}
- //only log homepage display upon a fresh launch
+ final MetricsFeatureProvider metricsFeatureProvider =
+ FeatureFactory.getFactory(mContext).getMetricsFeatureProvider();
final long timeoutLimit = getCardLoaderTimeout(mContext);
if (loadTime <= timeoutLimit) {
onContextualCardUpdated(cards.stream()
.collect(groupingBy(ContextualCard::getCardType)));
+ } else {
+ // log timeout occurrence
+ metricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN,
+ SettingsEnums.ACTION_CONTEXTUAL_CARD_LOAD_TIMEOUT,
+ SettingsEnums.SETTINGS_HOMEPAGE,
+ null /* key */, (int) loadTime /* value */);
}
+ //only log homepage display upon a fresh launch
final long totalTime = System.currentTimeMillis() - mStartTime;
FeatureFactory.getFactory(mContext).getContextualCardFeatureProvider(mContext)
.logHomepageDisplay(totalTime);
+ metricsFeatureProvider.action(mContext,
+ SettingsEnums.ACTION_CONTEXTUAL_HOME_SHOW, (int) totalTime);
mIsFirstLaunch = false;
}
diff --git a/src/com/android/settings/homepage/contextualcards/EligibleCardChecker.java b/src/com/android/settings/homepage/contextualcards/EligibleCardChecker.java
index fe68d028533..811aaa2fa13 100644
--- a/src/com/android/settings/homepage/contextualcards/EligibleCardChecker.java
+++ b/src/com/android/settings/homepage/contextualcards/EligibleCardChecker.java
@@ -18,6 +18,7 @@ package com.android.settings.homepage.contextualcards;
import static android.app.slice.Slice.HINT_ERROR;
+import android.app.settings.SettingsEnums;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
@@ -27,6 +28,9 @@ import androidx.annotation.VisibleForTesting;
import androidx.slice.Slice;
import androidx.slice.SliceViewManager;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -46,7 +50,32 @@ public class EligibleCardChecker implements Callable {
@Override
public ContextualCard call() throws Exception {
- return isCardEligibleToDisplay(mCard) ? mCard : null;
+ final long startTime = System.currentTimeMillis();
+ final MetricsFeatureProvider metricsFeatureProvider =
+ FeatureFactory.getFactory(mContext).getMetricsFeatureProvider();
+ ContextualCard result;
+
+ if (isCardEligibleToDisplay(mCard)) {
+ metricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN,
+ SettingsEnums.ACTION_CONTEXTUAL_CARD_ELIGIBILITY,
+ SettingsEnums.SETTINGS_HOMEPAGE,
+ mCard.getTextSliceUri() /* key */, 1 /* true */);
+ result = mCard;
+ } else {
+ metricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN,
+ SettingsEnums.ACTION_CONTEXTUAL_CARD_ELIGIBILITY,
+ SettingsEnums.SETTINGS_HOMEPAGE,
+ mCard.getTextSliceUri() /* key */, 0 /* false */);
+ result = null;
+ }
+ // Log individual card loading time
+ metricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN,
+ SettingsEnums.ACTION_CONTEXTUAL_CARD_LOAD,
+ SettingsEnums.SETTINGS_HOMEPAGE,
+ mCard.getTextSliceUri() /* key */,
+ (int) (System.currentTimeMillis() - startTime) /* value */);
+
+ return result;
}
@VisibleForTesting
diff --git a/src/com/android/settings/homepage/contextualcards/conditional/ConditionManager.java b/src/com/android/settings/homepage/contextualcards/conditional/ConditionManager.java
index c741b98c359..66f6c81e984 100644
--- a/src/com/android/settings/homepage/contextualcards/conditional/ConditionManager.java
+++ b/src/com/android/settings/homepage/contextualcards/conditional/ConditionManager.java
@@ -162,6 +162,7 @@ public class ConditionManager {
mCardControllers.add(new RingerVibrateConditionController(mAppContext, this /* manager */));
mCardControllers.add(new RingerMutedConditionController(mAppContext, this /* manager */));
mCardControllers.add(new WorkModeConditionController(mAppContext, this /* manager */));
+ mCardControllers.add(new GrayscaleConditionController(mAppContext, this /* manager */));
}
/**
diff --git a/src/com/android/settings/homepage/contextualcards/conditional/GrayscaleConditionController.java b/src/com/android/settings/homepage/contextualcards/conditional/GrayscaleConditionController.java
new file mode 100644
index 00000000000..664707def69
--- /dev/null
+++ b/src/com/android/settings/homepage/contextualcards/conditional/GrayscaleConditionController.java
@@ -0,0 +1,103 @@
+/*
+ * 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.homepage.contextualcards.conditional;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.display.ColorDisplayManager;
+import android.util.Log;
+
+import com.android.settings.R;
+import com.android.settings.homepage.contextualcards.ContextualCard;
+
+import java.net.URISyntaxException;
+import java.util.Objects;
+
+public class GrayscaleConditionController implements ConditionalCardController {
+ static final int ID = Objects.hash("GrayscaleConditionController");
+
+ private static final String TAG = "GrayscaleCondition";
+
+ private final Context mAppContext;
+ private final ConditionManager mConditionManager;
+ private final ColorDisplayManager mColorDisplayManager;
+
+ private Intent mIntent;
+
+ public GrayscaleConditionController(Context appContext, ConditionManager conditionManager) {
+ mAppContext = appContext;
+ mConditionManager = conditionManager;
+ mColorDisplayManager = mAppContext.getSystemService(ColorDisplayManager.class);
+ }
+
+ @Override
+ public long getId() {
+ return ID;
+ }
+
+ @Override
+ public boolean isDisplayable() {
+ try {
+ mIntent = Intent.parseUri(
+ mAppContext.getString(R.string.config_grayscale_settings_intent),
+ Intent.URI_INTENT_SCHEME);
+ } catch (URISyntaxException e) {
+ Log.w(TAG, "Failure parsing grayscale settings intent, skipping", e);
+ return false;
+ }
+ return mColorDisplayManager.isSaturationActivated();
+ }
+
+ @Override
+ public void onPrimaryClick(Context context) {
+ mAppContext.startActivity(mIntent);
+ }
+
+ @Override
+ public void onActionClick() {
+ // Turn off grayscale
+ mColorDisplayManager.setSaturationLevel(100 /* staturationLevel */);
+ mConditionManager.onConditionChanged();
+ }
+
+ @Override
+ public ContextualCard buildContextualCard() {
+ return new ConditionalContextualCard.Builder()
+ .setConditionId(ID)
+ .setMetricsConstant(SettingsEnums.SETTINGS_CONDITION_GRAYSCALE_MODE)
+ .setActionText(mAppContext.getText(R.string.condition_turn_off))
+ .setName(mAppContext.getPackageName() + "/" + mAppContext.getText(
+ R.string.condition_grayscale_title))
+ .setTitleText(mAppContext.getText(R.string.condition_grayscale_title).toString())
+ .setSummaryText(
+ mAppContext.getText(R.string.condition_grayscale_summary).toString())
+ .setIconDrawable(mAppContext.getDrawable(R.drawable.ic_gray_scale_24dp))
+ .setViewType(ConditionContextualCardRenderer.VIEW_TYPE_HALF_WIDTH)
+ .build();
+ }
+
+ @Override
+ public void startMonitoringStateChange() {
+
+ }
+
+ @Override
+ public void stopMonitoringStateChange() {
+
+ }
+}
diff --git a/src/com/android/settings/media/MediaOutputIndicatorSlice.java b/src/com/android/settings/media/MediaOutputIndicatorSlice.java
new file mode 100644
index 00000000000..eb0c81f1c2c
--- /dev/null
+++ b/src/com/android/settings/media/MediaOutputIndicatorSlice.java
@@ -0,0 +1,104 @@
+/*
+ * 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.media;
+
+import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI;
+
+import android.annotation.ColorInt;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.core.graphics.drawable.IconCompat;
+import androidx.slice.Slice;
+import androidx.slice.builders.ListBuilder;
+import androidx.slice.builders.SliceAction;
+
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.slices.CustomSliceable;
+import com.android.settings.slices.SliceBackgroundWorker;
+import com.android.settingslib.media.MediaOutputSliceConstants;
+
+public class MediaOutputIndicatorSlice implements CustomSliceable {
+
+ private Context mContext;
+ @VisibleForTesting
+ MediaOutputIndicatorWorker mWorker;
+
+ public MediaOutputIndicatorSlice(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public Slice getSlice() {
+ if (!getWorker().isVisible()) {
+ return null;
+ }
+ final IconCompat icon = IconCompat.createWithResource(mContext,
+ com.android.internal.R.drawable.ic_settings_bluetooth);
+ final CharSequence title = mContext.getText(R.string.media_output_title);
+ final PendingIntent primaryActionIntent = PendingIntent.getActivity(mContext,
+ 0 /* requestCode */, getMediaOutputSliceIntent(), 0 /* flags */);
+ final SliceAction primarySliceAction = SliceAction.createDeeplink(
+ primaryActionIntent, icon, ListBuilder.ICON_IMAGE, title);
+ @ColorInt final int color = Utils.getColorAccentDefaultColor(mContext);
+
+ final ListBuilder listBuilder = new ListBuilder(mContext,
+ MEDIA_OUTPUT_INDICATOR_SLICE_URI,
+ ListBuilder.INFINITY)
+ .setAccentColor(color)
+ .addRow(new ListBuilder.RowBuilder()
+ .setTitle(title)
+ .setSubtitle(getWorker().findActiveDeviceName())
+ .setPrimaryAction(primarySliceAction));
+ return listBuilder.build();
+ }
+
+ private MediaOutputIndicatorWorker getWorker() {
+ if (mWorker == null) {
+ mWorker = (MediaOutputIndicatorWorker) SliceBackgroundWorker.getInstance(getUri());
+ }
+ return mWorker;
+ }
+
+ private Intent getMediaOutputSliceIntent() {
+ final Intent intent = new Intent()
+ .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ return intent;
+ }
+
+ @Override
+ public Uri getUri() {
+ return MEDIA_OUTPUT_INDICATOR_SLICE_URI;
+ }
+
+ @Override
+ public Intent getIntent() {
+ // This Slice reflects active media device information and launch MediaOutputSlice. It does
+ // not contain its owned Slice data
+ return null;
+ }
+
+ @Override
+ public Class getBackgroundWorkerClass() {
+ return MediaOutputIndicatorWorker.class;
+ }
+}
diff --git a/src/com/android/settings/media/MediaOutputIndicatorWorker.java b/src/com/android/settings/media/MediaOutputIndicatorWorker.java
new file mode 100644
index 00000000000..adee0557be9
--- /dev/null
+++ b/src/com/android/settings/media/MediaOutputIndicatorWorker.java
@@ -0,0 +1,161 @@
+/*
+ * 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.media;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.net.Uri;
+import android.util.Log;
+
+import com.android.internal.util.CollectionUtils;
+import com.android.settings.R;
+import com.android.settings.bluetooth.Utils;
+import com.android.settings.slices.SliceBackgroundWorker;
+import com.android.settingslib.bluetooth.A2dpProfile;
+import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.HearingAidProfile;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Listener for background change from {@code BluetoothCallback} to update media output indicator.
+ */
+public class MediaOutputIndicatorWorker extends SliceBackgroundWorker implements BluetoothCallback {
+
+ private static final String TAG = "MediaOutputIndicatorWorker";
+
+ private LocalBluetoothManager mLocalBluetoothManager;
+ private LocalBluetoothProfileManager mProfileManager;
+
+ public MediaOutputIndicatorWorker(Context context, Uri uri) {
+ super(context, uri);
+ }
+
+ @Override
+ protected void onSlicePinned() {
+ LocalBluetoothManager mLocalBluetoothManager = Utils.getLocalBtManager(getContext());
+ if (mLocalBluetoothManager == null) {
+ Log.e(TAG, "Bluetooth is not supported on this device");
+ return;
+ }
+ mProfileManager = mLocalBluetoothManager.getProfileManager();
+ mLocalBluetoothManager.getEventManager().registerCallback(this);
+ }
+
+ @Override
+ protected void onSliceUnpinned() {
+ if (mLocalBluetoothManager == null) {
+ Log.e(TAG, "Bluetooth is not supported on this device");
+ return;
+ }
+ mLocalBluetoothManager.getEventManager().unregisterCallback(this);
+ }
+
+ @Override
+ public void close() throws IOException {
+ mLocalBluetoothManager = null;
+ mProfileManager = null;
+ }
+
+ @Override
+ public void onBluetoothStateChanged(int bluetoothState) {
+ // To handle the case that Bluetooth on and no connected devices
+ notifySliceChange();
+ }
+
+ @Override
+ public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
+ if (bluetoothProfile == BluetoothProfile.A2DP) {
+ notifySliceChange();
+ }
+ }
+
+ /**
+ * To decide Slice's visibility.
+ *
+ * @return true if device is connected or previously connected, false for other cases.
+ */
+ public boolean isVisible() {
+ return !CollectionUtils.isEmpty(getConnectableA2dpDevices())
+ || !CollectionUtils.isEmpty(getConnectableHearingAidDevices());
+ }
+
+ private List getConnectableA2dpDevices() {
+ // get A2dp devices on all states
+ // (STATE_DISCONNECTED, STATE_CONNECTING, STATE_CONNECTED, STATE_DISCONNECTING)
+ final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile();
+ if (a2dpProfile == null) {
+ return new ArrayList<>();
+ }
+ return a2dpProfile.getConnectableDevices();
+ }
+
+ private List getConnectableHearingAidDevices() {
+ // get hearing aid profile devices on all states
+ // (STATE_DISCONNECTED, STATE_CONNECTING, STATE_CONNECTED, STATE_DISCONNECTING)
+ final HearingAidProfile hapProfile = mProfileManager.getHearingAidProfile();
+ if (hapProfile == null) {
+ return new ArrayList<>();
+ }
+
+ return hapProfile.getConnectableDevices();
+ }
+
+ /**
+ * Get active devices name.
+ *
+ * @return active Bluetooth device alias, or default summary if no active device.
+ */
+ public CharSequence findActiveDeviceName() {
+ // Return Hearing Aid device name if it is active
+ BluetoothDevice activeDevice = findActiveHearingAidDevice();
+ if (activeDevice != null) {
+ return activeDevice.getAliasName();
+ }
+ // Return A2DP device name if it is active
+ final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile();
+ if (a2dpProfile != null) {
+ activeDevice = a2dpProfile.getActiveDevice();
+ if (activeDevice != null) {
+ return activeDevice.getAliasName();
+ }
+ }
+ // No active device, return default summary
+ return getContext().getText(R.string.media_output_default_summary);
+ }
+
+ private BluetoothDevice findActiveHearingAidDevice() {
+ final HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile();
+ if (hearingAidProfile == null) {
+ return null;
+ }
+
+ final List activeDevices = hearingAidProfile.getActiveDevices();
+ for (BluetoothDevice btDevice : activeDevices) {
+ if (btDevice != null) {
+ return btDevice;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/settings/notification/ZenAccessSettings.java b/src/com/android/settings/notification/ZenAccessSettings.java
index d057c755754..fca82552244 100644
--- a/src/com/android/settings/notification/ZenAccessSettings.java
+++ b/src/com/android/settings/notification/ZenAccessSettings.java
@@ -18,56 +18,40 @@ package com.android.settings.notification;
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 @@ public class ZenAccessSettings extends EmptyTextSettings {
mContext = getActivity();
mPkgMan = mContext.getPackageManager();
mNoMan = mContext.getSystemService(NotificationManager.class);
+ getSettingsLifecycle().addObserver(
+ new ZenAccessSettingObserverMixin(getContext(), this /* listener */));
}
@Override
@@ -102,30 +88,22 @@ public class ZenAccessSettings extends EmptyTextSettings {
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 apps = new ArrayList<>();
- final ArraySet requesting = getPackagesRequestingNotificationPolicyAccess();
+ final Set requesting =
+ ZenAccessController.getPackagesRequestingNotificationPolicyAccess();
if (!requesting.isEmpty()) {
final List installed = mPkgMan.getInstalledApplications(0);
if (installed != null) {
@@ -143,204 +121,42 @@ public class ZenAccessSettings extends EmptyTextSettings {
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 getPackagesRequestingNotificationPolicyAccess() {
- ArraySet 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 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/panel/VolumePanel.java b/src/com/android/settings/panel/VolumePanel.java
index 62eca53898b..4ea7fe7561b 100644
--- a/src/com/android/settings/panel/VolumePanel.java
+++ b/src/com/android/settings/panel/VolumePanel.java
@@ -16,6 +16,7 @@
package com.android.settings.panel;
+import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI;
import static com.android.settings.slices.CustomSliceRegistry.VOLUME_ALARM_URI;
import static com.android.settings.slices.CustomSliceRegistry.VOLUME_CALL_URI;
import static com.android.settings.slices.CustomSliceRegistry.VOLUME_MEDIA_URI;
@@ -55,6 +56,7 @@ public class VolumePanel implements PanelContent {
final List uris = new ArrayList<>();
uris.add(VOLUME_REMOTE_MEDIA_URI);
uris.add(VOLUME_MEDIA_URI);
+ uris.add(MEDIA_OUTPUT_INDICATOR_SLICE_URI);
uris.add(VOLUME_CALL_URI);
uris.add(VOLUME_RINGER_URI);
uris.add(VOLUME_ALARM_URI);
diff --git a/src/com/android/settings/slices/CustomSliceRegistry.java b/src/com/android/settings/slices/CustomSliceRegistry.java
index 9d88bc587e9..6b1ead729c3 100644
--- a/src/com/android/settings/slices/CustomSliceRegistry.java
+++ b/src/com/android/settings/slices/CustomSliceRegistry.java
@@ -39,6 +39,7 @@ import com.android.settings.homepage.contextualcards.slices.BluetoothDevicesSlic
import com.android.settings.homepage.contextualcards.slices.LowStorageSlice;
import com.android.settings.homepage.contextualcards.slices.NotificationChannelSlice;
import com.android.settings.location.LocationSlice;
+import com.android.settings.media.MediaOutputIndicatorSlice;
import com.android.settings.media.MediaOutputSlice;
import com.android.settings.network.telephony.MobileDataSlice;
import com.android.settings.wifi.calling.WifiCallingSliceHelper;
@@ -299,6 +300,16 @@ public class CustomSliceRegistry {
.appendPath(MediaOutputSliceConstants.KEY_MEDIA_OUTPUT)
.build();
+ /**
+ * Backing Uri for the Media output indicator Slice.
+ */
+ public static Uri MEDIA_OUTPUT_INDICATOR_SLICE_URI = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(SettingsSliceProvider.SLICE_AUTHORITY)
+ .appendPath(SettingsSlicesContract.PATH_SETTING_INTENT)
+ .appendPath("media_output_indicator")
+ .build();
+
@VisibleForTesting
static final Map> sUriToSlice;
@@ -319,6 +330,7 @@ public class CustomSliceRegistry {
sUriToSlice.put(STORAGE_SLICE_URI, StorageSlice.class);
sUriToSlice.put(WIFI_SLICE_URI, WifiSlice.class);
sUriToSlice.put(MEDIA_OUTPUT_SLICE_URI, MediaOutputSlice.class);
+ sUriToSlice.put(MEDIA_OUTPUT_INDICATOR_SLICE_URI, MediaOutputIndicatorSlice.class);
}
public static Class extends CustomSliceable> getSliceClassByUri(Uri uri) {
@@ -344,5 +356,4 @@ public class CustomSliceRegistry {
public static boolean isValidAction(String action) {
return isValidUri(Uri.parse(action));
}
-
}
diff --git a/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java b/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java
index 89565dfb203..5588977554b 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;
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.core.lifecycle.events.OnResume;
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 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
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 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
// fall through
case WifiManager.NETWORK_STATE_CHANGED_ACTION:
case WifiManager.RSSI_CHANGED_ACTION:
- updateLiveNetworkInfo();
+ updateNetworkInfo();
break;
}
}
@@ -206,14 +211,16 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
@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 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
}
};
+ 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 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
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 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
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 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
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 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
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 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
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 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
}
}
- 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 3407890aaf3..00000000000
--- 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 7edd227a559..66587edb54d 100644
--- a/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java
+++ b/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java
@@ -51,9 +51,6 @@ public class WifiNetworkDetailsFragment extends DashboardFragment {
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 @@ public class WifiNetworkDetailsFragment extends DashboardFragment {
final List 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 ea858f32361..3f600e6772d 100644
--- a/src/com/android/settings/wifi/savedaccesspoints/SavedAccessPointsWifiSettings.java
+++ b/src/com/android/settings/wifi/savedaccesspoints/SavedAccessPointsWifiSettings.java
@@ -108,7 +108,6 @@ public class SavedAccessPointsWifiSettings extends DashboardFragment
}
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/res/values-mcc999/config.xml b/tests/robotests/res/values-mcc999/config.xml
index 776a4d0902f..1fe4bbeecf0 100644
--- a/tests/robotests/res/values-mcc999/config.xml
+++ b/tests/robotests/res/values-mcc999/config.xml
@@ -91,4 +91,7 @@
test@test.test
+
+
+ intent:#Intent;action=test.test;end
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 bcb4bb3bf4a..6041e9dead1 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 @@ package com.android.settings.applications.specialaccess.zenaccess;
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 @@ public class ZenAccessControllerTest {
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 00000000000..cba1a5199ed
--- /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/homepage/contextualcards/conditional/GrayscaleConditionControllerTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/GrayscaleConditionControllerTest.java
new file mode 100644
index 00000000000..8c24735c77a
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/GrayscaleConditionControllerTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.homepage.contextualcards.conditional;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.hardware.display.ColorDisplayManager;
+
+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.annotation.Config;
+
+@RunWith(RobolectricTestRunner.class)
+public class GrayscaleConditionControllerTest {
+
+ @Mock
+ private ConditionManager mConditionManager;
+
+ private ColorDisplayManager mColorDisplayManager;
+ private Context mContext;
+ private GrayscaleConditionController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(RuntimeEnvironment.application);
+ mColorDisplayManager = spy(mContext.getSystemService(ColorDisplayManager.class));
+ doReturn(mColorDisplayManager).when(mContext).getSystemService(ColorDisplayManager.class);
+ mController = new GrayscaleConditionController(mContext, mConditionManager);
+ }
+
+ @Test
+ public void isDisplayable_noIntent_shouldReturnFalse() {
+ assertThat(mController.isDisplayable()).isFalse();
+ }
+
+ @Test
+ @Config(qualifiers = "mcc999")
+ public void isDisplayable_validIntentAndGrayscaleOn_shouldReturnTrue() {
+ doReturn(true).when(mColorDisplayManager).isSaturationActivated();
+
+ assertThat(mController.isDisplayable()).isTrue();
+ }
+
+ @Test
+ @Config(qualifiers = "mcc999")
+ public void isDisplayable_validIntentAndGrayscaleOff_shouldReturnFalse() {
+ doReturn(false).when(mColorDisplayManager).isSaturationActivated();
+
+ assertThat(mController.isDisplayable()).isFalse();
+ }
+
+ @Test
+ public void onActionClick_shouldRefreshCondition() {
+ mController.onActionClick();
+
+ verify(mConditionManager).onConditionChanged();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorSliceTest.java b/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorSliceTest.java
new file mode 100644
index 00000000000..ab3f4de26f9
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorSliceTest.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.media;
+
+import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.slice.Slice;
+import androidx.slice.SliceItem;
+import androidx.slice.SliceMetadata;
+import androidx.slice.SliceProvider;
+import androidx.slice.core.SliceAction;
+import androidx.slice.widget.SliceLiveData;
+
+import com.android.settings.R;
+import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
+import com.android.settingslib.media.LocalMediaManager;
+import com.android.settingslib.media.MediaDevice;
+import com.android.settingslib.media.MediaOutputSliceConstants;
+
+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.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class})
+public class MediaOutputIndicatorSliceTest {
+
+ private static final String TEST_DEVICE_NAME = "test_device_name";
+ private static final int TEST_DEVICE_1_ICON =
+ com.android.internal.R.drawable.ic_bt_headphones_a2dp;
+
+ @Mock
+ private LocalMediaManager mLocalMediaManager;
+
+ private final List mDevices = new ArrayList<>();
+
+ private Context mContext;
+ private MediaOutputIndicatorSlice mMediaOutputIndicatorSlice;
+ private MediaOutputIndicatorWorker mMediaOutputIndicatorWorker;
+ private ShadowBluetoothAdapter mShadowBluetoothAdapter;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(RuntimeEnvironment.application);
+
+ // Set-up specs for SliceMetadata.
+ SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
+
+ mMediaOutputIndicatorSlice = new MediaOutputIndicatorSlice(mContext);
+ mMediaOutputIndicatorWorker = spy(new MediaOutputIndicatorWorker(
+ mContext, MEDIA_OUTPUT_INDICATOR_SLICE_URI));
+ mMediaOutputIndicatorSlice.mWorker = mMediaOutputIndicatorWorker;
+ }
+
+ @Test
+ public void getSlice_invisible_returnNull() {
+ when(mMediaOutputIndicatorWorker.isVisible()).thenReturn(false);
+
+ assertThat(mMediaOutputIndicatorSlice.getSlice()).isNull();
+ }
+
+ @Test
+ public void getSlice_withActiveDevice_checkContent() {
+ when(mMediaOutputIndicatorWorker.isVisible()).thenReturn(true);
+ when(mMediaOutputIndicatorWorker.findActiveDeviceName()).thenReturn(TEST_DEVICE_NAME);
+ final Slice mediaSlice = mMediaOutputIndicatorSlice.getSlice();
+ final SliceMetadata metadata = SliceMetadata.from(mContext, mediaSlice);
+ // Verify slice title and subtitle
+ assertThat(metadata.getTitle()).isEqualTo(mContext.getText(R.string.media_output_title));
+ assertThat(metadata.getSubtitle()).isEqualTo(TEST_DEVICE_NAME);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java b/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java
new file mode 100644
index 00000000000..4a5662e53de
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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.media;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.content.Context;
+import android.net.Uri;
+
+import com.android.settings.R;
+import com.android.settings.bluetooth.Utils;
+import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
+import com.android.settingslib.bluetooth.A2dpProfile;
+import com.android.settingslib.bluetooth.BluetoothEventManager;
+import com.android.settingslib.bluetooth.HearingAidProfile;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+
+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.annotation.Config;
+import org.robolectric.shadows.ShadowBluetoothDevice;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothUtils.class,
+ ShadowBluetoothDevice.class})
+public class MediaOutputIndicatorWorkerTest {
+
+ private static final String TEST_A2DP_DEVICE_NAME = "Test_A2DP_BT_Device_NAME";
+ private static final String TEST_HAP_DEVICE_NAME = "Test_HAP_BT_Device_NAME";
+ private static final String TEST_A2DP_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1";
+ private static final String TEST_HAP_DEVICE_ADDRESS = "00:B2:B2:B2:B2:B2";
+ private static final Uri URI = Uri.parse("content://com.android.settings.slices/test");
+
+ @Mock
+ private A2dpProfile mA2dpProfile;
+ @Mock
+ private HearingAidProfile mHearingAidProfile;
+ @Mock
+ private LocalBluetoothManager mLocalManager;
+ @Mock
+ private BluetoothEventManager mBluetoothEventManager;
+ @Mock
+ private LocalBluetoothProfileManager mLocalBluetoothProfileManager;
+
+ private BluetoothAdapter mBluetoothAdapter;
+ private BluetoothDevice mA2dpDevice;
+ private BluetoothDevice mHapDevice;
+ private BluetoothManager mBluetoothManager;
+ private Context mContext;
+ private List mDevicesList;
+ private LocalBluetoothManager mLocalBluetoothManager;
+ private MediaOutputIndicatorWorker mMediaDeviceUpdateWorker;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(RuntimeEnvironment.application);
+ ShadowBluetoothUtils.sLocalBluetoothManager = mLocalManager;
+ mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
+ when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
+ when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
+ when(mLocalBluetoothProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
+ when(mLocalBluetoothProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
+ mBluetoothManager = new BluetoothManager(mContext);
+ mBluetoothAdapter = mBluetoothManager.getAdapter();
+
+ // Setup A2dp device
+ mA2dpDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_A2DP_DEVICE_ADDRESS));
+ when(mA2dpDevice.getName()).thenReturn(TEST_A2DP_DEVICE_NAME);
+ when(mA2dpDevice.isConnected()).thenReturn(true);
+ // Setup HearingAid device
+ mHapDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_HAP_DEVICE_ADDRESS));
+ when(mHapDevice.getName()).thenReturn(TEST_HAP_DEVICE_NAME);
+ when(mHapDevice.isConnected()).thenReturn(true);
+
+ mMediaDeviceUpdateWorker = new MediaOutputIndicatorWorker(mContext, URI);
+ mDevicesList = new ArrayList<>();
+ }
+
+ @Test
+ public void isVisible_noConnectableDevice_returnFalse() {
+ mDevicesList.clear();
+ when(mA2dpProfile.getConnectableDevices()).thenReturn(mDevicesList);
+
+ assertThat(mMediaDeviceUpdateWorker.isVisible()).isFalse();
+ }
+
+ @Test
+ public void isVisible_withConnectableA2dpDevice_returnTrue() {
+ mDevicesList.clear();
+ mDevicesList.add(mA2dpDevice);
+ when(mHearingAidProfile.getConnectableDevices()).thenReturn(mDevicesList);
+
+ assertThat(mMediaDeviceUpdateWorker.isVisible()).isTrue();
+ }
+
+ @Test
+ public void isVisible_withConnectableHADevice_returnTrue() {
+ mDevicesList.clear();
+ mDevicesList.add(mHapDevice);
+ when(mA2dpProfile.getConnectableDevices()).thenReturn(mDevicesList);
+
+ assertThat(mMediaDeviceUpdateWorker.isVisible()).isTrue();
+ }
+
+ @Test
+ public void findActiveDeviceName_A2dpDeviceActive_verifyName() {
+ when(mA2dpProfile.getActiveDevice()).thenReturn(mA2dpDevice);
+
+ assertThat(mMediaDeviceUpdateWorker.findActiveDeviceName())
+ .isEqualTo(mA2dpDevice.getAliasName());
+ }
+
+ @Test
+ public void findActiveDeviceName_HADeviceActive_verifyName() {
+ mDevicesList.add(mHapDevice);
+ when(mHearingAidProfile.getActiveDevices()).thenReturn(mDevicesList);
+
+ assertThat(mMediaDeviceUpdateWorker.findActiveDeviceName())
+ .isEqualTo(mHapDevice.getAliasName());
+ }
+
+ @Test
+ public void findActiveDeviceName_noActiveDevice_verifyDefaultName() {
+ when(mA2dpProfile.getActiveDevice()).thenReturn(null);
+ mDevicesList.clear();
+ when(mHearingAidProfile.getActiveDevices()).thenReturn(mDevicesList);
+
+ assertThat(mMediaDeviceUpdateWorker.findActiveDeviceName())
+ .isEqualTo(mContext.getText(R.string.media_output_default_summary));
+ }
+}
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 c2a6f4f892c..00000000000
--- 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/panel/VolumePanelTest.java b/tests/robotests/src/com/android/settings/panel/VolumePanelTest.java
index 4665dc9b4fd..11de7b31b60 100644
--- a/tests/robotests/src/com/android/settings/panel/VolumePanelTest.java
+++ b/tests/robotests/src/com/android/settings/panel/VolumePanelTest.java
@@ -48,6 +48,7 @@ public class VolumePanelTest {
CustomSliceRegistry.VOLUME_REMOTE_MEDIA_URI,
CustomSliceRegistry.VOLUME_CALL_URI,
CustomSliceRegistry.VOLUME_MEDIA_URI,
+ CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI,
CustomSliceRegistry.VOLUME_RINGER_URI,
CustomSliceRegistry.VOLUME_ALARM_URI);
}
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 83257776289..78fb23f3133 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 @@ package com.android.settings.testutils.shadow;
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 mNotificationPolicyGrantedPackages = new ArraySet<>();
@Implementation
protected void setZenMode(int mode, Uri conditionId, String reason) {
@@ -39,6 +43,11 @@ public class ShadowNotificationManager {
return mZenMode;
}
+ @Implementation
+ protected boolean isNotificationPolicyAccessGrantedForPackage(String pkg) {
+ return mNotificationPolicyGrantedPackages.contains(pkg);
+ }
+
@Implementation
public ZenModeConfig getZenModeConfig() {
return mZenModeConfig;
@@ -47,4 +56,8 @@ public class ShadowNotificationManager {
public void setZenModeConfig(ZenModeConfig config) {
mZenModeConfig = config;
}
+
+ public void setNotificationPolicyAccessGrantedForPackage(String pkg) {
+ mNotificationPolicyGrantedPackages.add(pkg);
+ }
}