<!-- Sound & notification > Advanced section: Title for managing Do Not Disturb access option. [CHAR LIMIT=40] -->
<string name="manage_zen_access_title">Do Not Disturb access</string>
+ <!-- Button title that grants 'Do Not Disturb' permission to an app [CHAR_LIMIT=60]-->
+ <string name="zen_access_detail_switch">Allow Do Not Disturb</string>
+
<!-- Sound & notification > Do Not Disturb access > Text to display when the list is empty. [CHAR LIMIT=NONE] -->
<string name="zen_access_empty_text">No installed apps have requested Do Not Disturb access</string>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:key="zen_access_permission_detail_settings"
+ android:title="@string/manage_zen_access_title">
+
+ <SwitchPreference
+ android:key="zen_access_switch"
+ android:title="@string/zen_access_detail_switch"/>
+
+</PreferenceScreen>
\ No newline at end of file
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
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) {
@Override
public int getAvailabilityStatus() {
- return !mActivityManager.isLowRamDevice()
+ return isSupported(mActivityManager)
? AVAILABLE_UNSEARCHABLE
: UNSUPPORTED_ON_DEVICE;
}
+
+ public static boolean isSupported(ActivityManager activityManager) {
+ return !activityManager.isLowRamDevice();
+ }
+
+ public static Set<String> getPackagesRequestingNotificationPolicyAccess() {
+ final ArraySet<String> requestingPackages = new ArraySet<>();
+ try {
+ final String[] PERM = {
+ android.Manifest.permission.ACCESS_NOTIFICATION_POLICY
+ };
+ final ParceledListSlice list = AppGlobals.getPackageManager()
+ .getPackagesHoldingPermissions(PERM, 0 /*flags*/,
+ ActivityManager.getCurrentUser());
+ final List<PackageInfo> pkgs = list.getList();
+ if (pkgs != null) {
+ for (PackageInfo info : pkgs) {
+ requestingPackages.add(info.packageName);
+ }
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Cannot reach packagemanager", e);
+ }
+ return requestingPackages;
+ }
+
+ public static Set<String> getAutoApprovedPackages(Context context) {
+ final Set<String> autoApproved = new ArraySet<>();
+ autoApproved.addAll(context.getSystemService(NotificationManager.class)
+ .getEnabledNotificationListenerPackages());
+ return autoApproved;
+ }
+
+ public static boolean hasAccess(Context context, String pkg) {
+ return context.getSystemService(
+ NotificationManager.class).isNotificationPolicyAccessGrantedForPackage(pkg);
+ }
+
+ public static void setAccess(final Context context, final String pkg, final boolean access) {
+ logSpecialPermissionChange(access, pkg, context);
+ AsyncTask.execute(() -> {
+ final NotificationManager mgr = context.getSystemService(NotificationManager.class);
+ mgr.setNotificationPolicyAccessGranted(pkg, access);
+ });
+ }
+
+ public static void deleteRules(final Context context, final String pkg) {
+ AsyncTask.execute(() -> {
+ final NotificationManager mgr = context.getSystemService(NotificationManager.class);
+ mgr.removeAutomaticZenRules(pkg);
+ });
+ }
+
+ @VisibleForTesting
+ static void logSpecialPermissionChange(boolean enable, String packageName, Context context) {
+ int logCategory = enable ? SettingsEnums.APP_SPECIAL_PERMISSION_DND_ALLOW
+ : SettingsEnums.APP_SPECIAL_PERMISSION_DND_DENY;
+ FeatureFactory.getFactory(context).getMetricsFeatureProvider().action(context,
+ logCategory, packageName);
+ }
}
--- /dev/null
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications.specialaccess.zenaccess;
+
+import android.app.ActivityManager;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.preference.SwitchPreference;
+
+import com.android.settings.R;
+import com.android.settings.applications.AppInfoWithHeader;
+
+import java.util.Set;
+
+public class ZenAccessDetails extends AppInfoWithHeader implements
+ ZenAccessSettingObserverMixin.Listener {
+
+ private static final String SWITCH_PREF_KEY = "zen_access_switch";
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.ZEN_ACCESS_DETAIL;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.zen_access_permission_details);
+ getSettingsLifecycle().addObserver(
+ new ZenAccessSettingObserverMixin(getContext(), this /* listener */));
+ }
+
+ @Override
+ protected boolean refreshUi() {
+ final Context context = getContext();
+ if (!ZenAccessController.isSupported(context.getSystemService(ActivityManager.class))) {
+ return false;
+ }
+ // If this app didn't declare this permission in their manifest, don't bother showing UI.
+ final Set<String> needAccessApps =
+ ZenAccessController.getPackagesRequestingNotificationPolicyAccess();
+ if (!needAccessApps.contains(mPackageName)) {
+ return false;
+ }
+ updatePreference(context, findPreference(SWITCH_PREF_KEY));
+ return true;
+ }
+
+ @Override
+ protected AlertDialog createDialog(int id, int errorCode) {
+ return null;
+ }
+
+ public void updatePreference(Context context, SwitchPreference preference) {
+ final CharSequence label = mPackageInfo.applicationInfo.loadLabel(mPm);
+ final Set<String> autoApproved = ZenAccessController.getAutoApprovedPackages(context);
+ if (autoApproved.contains(mPackageName)) {
+ //Auto approved, user cannot do anything. Hard code summary and disable preference.
+ preference.setEnabled(false);
+ preference.setSummary(getString(R.string.zen_access_disabled_package_warning));
+ return;
+ }
+ preference.setChecked(ZenAccessController.hasAccess(context, mPackageName));
+ preference.setOnPreferenceChangeListener((p, newValue) -> {
+ final boolean access = (Boolean) newValue;
+ if (access) {
+ new ScaryWarningDialogFragment()
+ .setPkgInfo(mPackageName, label)
+ .show(getFragmentManager(), "dialog");
+ } else {
+ new FriendlyWarningDialogFragment()
+ .setPkgInfo(mPackageName, label)
+ .show(getFragmentManager(), "dialog");
+ }
+ return false;
+ });
+ }
+
+ @Override
+ public void onZenAccessPolicyChanged() {
+ refreshUi();
+ }
+}
--- /dev/null
+/*
+ * 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 */);
+ }
+}
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;
mContext = getActivity();
mPkgMan = mContext.getPackageManager();
mNoMan = mContext.getSystemService(NotificationManager.class);
+ getSettingsLifecycle().addObserver(
+ new ZenAccessSettingObserverMixin(getContext(), this /* listener */));
}
@Override
super.onResume();
if (!ActivityManager.isLowRamDeviceStatic()) {
reloadList();
- getContentResolver().registerContentObserver(
- Secure.getUriFor(Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES), false,
- mObserver);
- getContentResolver().registerContentObserver(
- Secure.getUriFor(Secure.ENABLED_NOTIFICATION_LISTENERS), false,
- mObserver);
} else {
setEmptyText(R.string.disabled_low_ram_device);
}
}
@Override
- public void onPause() {
- super.onPause();
- if (!ActivityManager.isLowRamDeviceStatic()) {
- getContentResolver().unregisterContentObserver(mObserver);
- }
+ public void onZenAccessPolicyChanged() {
+ reloadList();
}
private void reloadList() {
final PreferenceScreen screen = getPreferenceScreen();
screen.removeAll();
final ArrayList<ApplicationInfo> apps = new ArrayList<>();
- final ArraySet<String> requesting = getPackagesRequestingNotificationPolicyAccess();
+ final Set<String> requesting =
+ ZenAccessController.getPackagesRequestingNotificationPolicyAccess();
if (!requesting.isEmpty()) {
final List<ApplicationInfo> installed = mPkgMan.getInstalledApplications(0);
if (installed != null) {
for (ApplicationInfo app : apps) {
final String pkg = app.packageName;
final CharSequence label = app.loadLabel(mPkgMan);
- final SwitchPreference pref = new AppSwitchPreference(getPrefContext());
+ final AppPreference pref = new AppPreference(getPrefContext());
pref.setKey(pkg);
- pref.setPersistent(false);
pref.setIcon(app.loadIcon(mPkgMan));
pref.setTitle(label);
- pref.setChecked(hasAccess(pkg));
if (autoApproved.contains(pkg)) {
+ //Auto approved, user cannot do anything. Hard code summary and disable preference.
pref.setEnabled(false);
pref.setSummary(getString(R.string.zen_access_disabled_package_warning));
+ } else {
+ // Not auto approved, update summary according to notification backend.
+ pref.setSummary(getPreferenceSummary(pkg));
}
- pref.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
- @Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- final boolean access = (Boolean) newValue;
- if (access) {
- new ScaryWarningDialogFragment()
- .setPkgInfo(pkg, label)
- .show(getFragmentManager(), "dialog");
- } else {
- new FriendlyWarningDialogFragment()
- .setPkgInfo(pkg, label)
- .show(getFragmentManager(), "dialog");
- }
- return false;
- }
+ pref.setOnPreferenceClickListener(preference -> {
+ AppInfoBase.startAppInfoFragment(
+ ZenAccessDetails.class /* fragment */,
+ R.string.manage_zen_access_title /* titleRes */,
+ pkg,
+ app.uid,
+ this /* source */,
+ -1 /* requestCode */,
+ getMetricsCategory() /* sourceMetricsCategory */);
+ return true;
});
- screen.addPreference(pref);
- }
- }
-
- private ArraySet<String> getPackagesRequestingNotificationPolicyAccess() {
- ArraySet<String> requestingPackages = new ArraySet<>();
- try {
- final String[] PERM = {
- android.Manifest.permission.ACCESS_NOTIFICATION_POLICY
- };
- final ParceledListSlice list = AppGlobals.getPackageManager()
- .getPackagesHoldingPermissions(PERM, 0 /*flags*/,
- ActivityManager.getCurrentUser());
- final List<PackageInfo> pkgs = list.getList();
- if (pkgs != null) {
- for (PackageInfo info : pkgs) {
- requestingPackages.add(info.packageName);
- }
- }
- } catch(RemoteException e) {
- Log.e(TAG, "Cannot reach packagemanager", e);
- }
- return requestingPackages;
- }
-
- private boolean hasAccess(String pkg) {
- return mNoMan.isNotificationPolicyAccessGrantedForPackage(pkg);
- }
-
- private static void setAccess(final Context context, final String pkg, final boolean access) {
- logSpecialPermissionChange(access, pkg, context);
- AsyncTask.execute(new Runnable() {
- @Override
- public void run() {
- final NotificationManager mgr = context.getSystemService(NotificationManager.class);
- mgr.setNotificationPolicyAccessGranted(pkg, access);
- }
- });
- }
-
- @VisibleForTesting
- static void logSpecialPermissionChange(boolean enable, String packageName, Context context) {
- int logCategory = enable ? SettingsEnums.APP_SPECIAL_PERMISSION_DND_ALLOW
- : SettingsEnums.APP_SPECIAL_PERMISSION_DND_DENY;
- FeatureFactory.getFactory(context).getMetricsFeatureProvider().action(context,
- logCategory, packageName);
- }
-
-
- private static void deleteRules(final Context context, final String pkg) {
- AsyncTask.execute(new Runnable() {
- @Override
- public void run() {
- final NotificationManager mgr = context.getSystemService(NotificationManager.class);
- mgr.removeAutomaticZenRules(pkg);
- }
- });
- }
-
- private final class SettingObserver extends ContentObserver {
- public SettingObserver() {
- super(new Handler(Looper.getMainLooper()));
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- reloadList();
- }
- }
-
- /**
- * Warning dialog when allowing zen access warning about the privileges being granted.
- */
- 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();
+ screen.addPreference(pref);
}
}
/**
- * Warning dialog when revoking zen access warning that zen rule instances will be deleted.
+ * @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 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 =
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));
}
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();
+ }
}
--- /dev/null
+/*
+ * 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();
+ }
+}
+++ /dev/null
-/*
- * 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"));
- }
-}
import android.app.NotificationManager;
import android.net.Uri;
import android.service.notification.ZenModeConfig;
+import android.util.ArraySet;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import java.util.Set;
+
@Implements(NotificationManager.class)
public class ShadowNotificationManager {
private int mZenMode;
private ZenModeConfig mZenModeConfig;
+ private Set<String> mNotificationPolicyGrantedPackages = new ArraySet<>();
@Implementation
protected void setZenMode(int mode, Uri conditionId, String reason) {
}
@Implementation
+ protected boolean isNotificationPolicyAccessGrantedForPackage(String pkg) {
+ return mNotificationPolicyGrantedPackages.contains(pkg);
+ }
+
+ @Implementation
public ZenModeConfig getZenModeConfig() {
return mZenModeConfig;
}
public void setZenModeConfig(ZenModeConfig config) {
mZenModeConfig = config;
}
+
+ public void setNotificationPolicyAccessGrantedForPackage(String pkg) {
+ mNotificationPolicyGrantedPackages.add(pkg);
+ }
}