Snap for 5389215 from e230d19810 to qt-release

Change-Id: Ifd21ff224d503a39b7e63d6705b21a9440b37490
This commit is contained in:
android-build-team Robot
2019-03-20 03:04:40 +00:00
43 changed files with 1771 additions and 469 deletions

View File

@@ -2389,6 +2389,19 @@
android:value="com.android.settings.applications.specialaccess.pictureinpicture.PictureInPictureDetails" />
</activity>
<activity
android:name="Settings$ZenAccessDetailSettingsActivity"
android:label="@string/manage_zen_access_title"
android:excludeFromRecents="true">
<intent-filter>
<action android:name="android.settings.NOTIFICATION_POLICY_ACCESS_DETAIL_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="package" />
</intent-filter>
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.applications.specialaccess.zenaccess.ZenAccessDetails" />
</activity>
<activity
android:name="Settings$ZenAccessSettingsActivity"
android:label="@string/manage_zen_access_title"

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:alpha="0.14"
android:color="?android:attr/colorForeground"/>
</selector>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke
android:width="1dp"
android:color="@color/circle_outline_color"/>
</shape>

View File

@@ -0,0 +1,25 @@
<!--
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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM11,19.93C7.06,19.44 4,16.08 4,12s3.05,-7.44 7,-7.93V19.93zM13,4.07C14.03,4.2 15,4.52 15.87,5H13V4.07zM13,7h5.24c0.25,0.31 0.48,0.65 0.68,1H13V7zM13,10h6.74c0.08,0.33 0.15,0.66 0.19,1H13V10zM13,19.93V19h2.87C15,19.48 14.03,19.8 13,19.93zM18.24,17H13v-1h5.92C18.72,16.35 18.49,16.69 18.24,17zM19.74,14H13v-1h6.93C19.89,13.34 19.82,13.67 19.74,14z"/>
</vector>

View File

@@ -26,9 +26,11 @@
android:id="@+id/header_icon"
android:layout_width="72dp"
android:layout_height="72dp"
android:scaleType="fitCenter"
android:layout_gravity="center_horizontal"
android:antialias="true"/>
android:antialias="true"
android:background="@drawable/circle_outline"
android:padding="8dp"
android:scaleType="fitCenter"/>
<TextView
android:id="@+id/header_title"

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- similar to preference_material.xml but textview for emoji country flag
instead of an ImageView -->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginTop="16dp"
android:textAppearance="@style/TextAppearance.CategoryTitle"
android:textColor="?android:attr/colorAccent"
android:paddingStart="@dimen/preference_no_icon_padding_start"/>

View File

@@ -356,4 +356,7 @@
<!-- Slice Uri to query nearby devices. -->
<string name="config_nearby_devices_slice_uri" translatable="false">content://com.google.android.gms.nearby.fastpair/device_status_list_item</string>
<!-- Grayscale settings intent -->
<string name="config_grayscale_settings_intent" translate="false"></string>
</resources>

View File

@@ -392,7 +392,7 @@
<dimen name="homepage_condition_header_indicator_padding_top">4dp</dimen>
<dimen name="homepage_condition_header_indicator_padding_start">16dp</dimen>
<dimen name="homepage_condition_header_indicator_padding_end">16dp</dimen>
<dimen name="homepage_condition_footer_height">44dp</dimen>
<dimen name="homepage_condition_footer_height">48dp</dimen>
<dimen name="homepage_condition_footer_padding_top">10dp</dimen>
<dimen name="homepage_condition_footer_padding_end">10dp</dimen>
<dimen name="homepage_condition_header_icon_width_height">24dp</dimen>

View File

@@ -4525,8 +4525,8 @@
<string name="keyboard_shortcuts_helper">Keyboard shortcuts helper</string>
<!-- Summary text for the 'keyboard shortcuts helper' dialog. [CHAR LIMIT=100] -->
<string name="keyboard_shortcuts_helper_summary">Display available shortcuts</string>
<!-- Title for the 'Work profile input & assistance' preference category inside Languages and inputs'. [CHAR LIMIT=50] -->
<string name="language_and_input_for_work_category_title">Work profile input &amp; assistance</string>
<!-- Title for the 'Work profile keyboards & tools' preference category inside Languages and inputs'. [CHAR LIMIT=50] -->
<string name="language_and_input_for_work_category_title">Work profile keyboards &amp; tools</string>
<!-- Title for the 'Virtual keyboards for work' preference. [CHAR LIMIT=45] -->
<string name="virtual_keyboards_for_work_title">Virtual keyboard for work</string>
@@ -7939,6 +7939,9 @@
<!-- Sound & notification > Advanced section: Title for managing Do Not Disturb access option. [CHAR LIMIT=40] -->
<string name="manage_zen_access_title">Do Not Disturb access</string>
<!-- Button title that grants 'Do Not Disturb' permission to an app [CHAR_LIMIT=60]-->
<string name="zen_access_detail_switch">Allow Do Not Disturb</string>
<!-- Sound & notification > Do Not Disturb access > Text to display when the list is empty. [CHAR LIMIT=NONE] -->
<string name="zen_access_empty_text">No installed apps have requested Do Not Disturb access</string>
@@ -9325,6 +9328,12 @@
<!-- Summary of condition that night display is on (renamed "Night Light" with title caps). [CHAR LIMIT=NONE] -->
<string name="condition_night_display_summary">Screen tinted amber</string>
<!-- Title of condition that gray scale is on [CHAR LIMIT=NONE] -->
<string name="condition_grayscale_title">Greyscale</string>
<!-- Summary of condition that gray scale is on [CHAR LIMIT=NONE] -->
<string name="condition_grayscale_summary">Display only in grey color</string>
<!-- Summary for the condition section on the dashboard, representing number of conditions. [CHAR LIMIT=10] -->
<string name="condition_summary" translatable="false"><xliff:g name="count" example="3">%1$d</xliff:g></string>
@@ -10759,7 +10768,7 @@
</plurals>
<!-- Title for notification channel slice. [CHAR LIMIT=NONE] -->
<string name="manage_app_notification">Manage <xliff:g id="app_name" example="Settings">%1$s</xliff:g> Notifications</string>
<string name="manage_app_notification">Manage <xliff:g id="app_name" example="Settings">%1$s</xliff:g> notifications</string>
<!-- Title for no suggested app in notification channel slice. [CHAR LIMIT=NONE] -->
<string name="no_suggested_app">No suggested application</string>
<!-- Summary for the channels count is equal or less than 3 in notification channel slice. [CHAR LIMIT=NONE] -->
@@ -10769,6 +10778,8 @@
</plurals>
<!-- Summary for the channels count is more than 3 in notification channel slice. [CHAR LIMIT=NONE] -->
<string name="notification_many_channel_count_summary"><xliff:g id="notification_channel_count" example="4">%1$d</xliff:g> notification channels. Tap to manage all.</string>
<!-- Summary for recently installed app in contextual notification channel slice. [CHAR LIMIT=NONE] -->
<string name="recently_installed_app">You recently installed this app.</string>
<!-- Title for the Switch output dialog (settings panel) with media related devices [CHAR LIMIT=50] -->
<string name="media_output_panel_title">Switch output</string>

View File

@@ -27,12 +27,6 @@
android:fragment="com.android.settings.gestures.AssistGestureSettings"
settings:controller="com.android.settings.gestures.AssistGestureSettingsPreferenceController" />
<Preference
android:key="gesture_wake_screen_input_summary"
android:title="@string/ambient_display_wake_screen_title"
android:fragment="com.android.settings.gestures.WakeScreenGestureSettings"
settings:controller="com.android.settings.gestures.WakeScreenGesturePreferenceController" />
<Preference
android:key="gesture_swipe_down_fingerprint_input_summary"
android:title="@string/fingerprint_swipe_for_notifications_title"

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:key="zen_access_permission_detail_settings"
android:title="@string/manage_zen_access_title">
<SwitchPreference
android:key="zen_access_switch"
android:title="@string/zen_access_detail_switch"/>
</PreferenceScreen>

View File

@@ -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 */ }

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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<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);
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.applications.specialaccess.zenaccess;
import android.app.ActivityManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.applications.AppInfoWithHeader;
import java.util.Set;
public class ZenAccessDetails extends AppInfoWithHeader implements
ZenAccessSettingObserverMixin.Listener {
private static final String SWITCH_PREF_KEY = "zen_access_switch";
@Override
public int getMetricsCategory() {
return SettingsEnums.ZEN_ACCESS_DETAIL;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.zen_access_permission_details);
getSettingsLifecycle().addObserver(
new ZenAccessSettingObserverMixin(getContext(), this /* listener */));
}
@Override
protected boolean refreshUi() {
final Context context = getContext();
if (!ZenAccessController.isSupported(context.getSystemService(ActivityManager.class))) {
return false;
}
// If this app didn't declare this permission in their manifest, don't bother showing UI.
final Set<String> needAccessApps =
ZenAccessController.getPackagesRequestingNotificationPolicyAccess();
if (!needAccessApps.contains(mPackageName)) {
return false;
}
updatePreference(context, findPreference(SWITCH_PREF_KEY));
return true;
}
@Override
protected AlertDialog createDialog(int id, int errorCode) {
return null;
}
public void updatePreference(Context context, SwitchPreference preference) {
final CharSequence label = mPackageInfo.applicationInfo.loadLabel(mPm);
final Set<String> autoApproved = ZenAccessController.getAutoApprovedPackages(context);
if (autoApproved.contains(mPackageName)) {
//Auto approved, user cannot do anything. Hard code summary and disable preference.
preference.setEnabled(false);
preference.setSummary(getString(R.string.zen_access_disabled_package_warning));
return;
}
preference.setChecked(ZenAccessController.hasAccess(context, mPackageName));
preference.setOnPreferenceChangeListener((p, newValue) -> {
final boolean access = (Boolean) newValue;
if (access) {
new ScaryWarningDialogFragment()
.setPkgInfo(mPackageName, label)
.show(getFragmentManager(), "dialog");
} else {
new FriendlyWarningDialogFragment()
.setPkgInfo(mPackageName, label)
.show(getFragmentManager(), "dialog");
}
return false;
});
}
@Override
public void onZenAccessPolicyChanged() {
refreshUi();
}
}

View File

@@ -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 */);
}
}

View File

@@ -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

View File

@@ -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(),

View File

@@ -77,9 +77,10 @@ public class BaseTimeZoneAdapter<T extends BaseTimeZoneAdapter.AdapterItem>
@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<T extends BaseTimeZoneAdapter.AdapterItem>
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<T extends BaseTimeZoneAdapter.AdapterItem>
public interface AdapterItem {
CharSequence getTitle();
CharSequence getSummary();
String getIconText();
String getCurrentTime();
/**
* @return unique non-negative number
*/
long getItemId();
String[] getSearchKeys();
}

View File

@@ -187,7 +187,6 @@ public class ContextualCardLoader extends AsyncLoaderCompat<List<ContextualCard>
// Collect future and eligible cards
for (Future<ContextualCard> 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) {

View File

@@ -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<ContextualCard> 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<ContextualCard> 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;
}

View File

@@ -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<ContextualCard> {
@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

View File

@@ -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 */));
}
/**

View File

@@ -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() {
}
}

View File

@@ -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;
}
}

View File

@@ -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<BluetoothDevice> 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<BluetoothDevice> 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<BluetoothDevice> activeDevices = hearingAidProfile.getActiveDevices();
for (BluetoothDevice btDevice : activeDevices) {
if (btDevice != null) {
return btDevice;
}
}
return null;
}
}

View File

@@ -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<ApplicationInfo> apps = new ArrayList<>();
final ArraySet<String> requesting = getPackagesRequestingNotificationPolicyAccess();
final Set<String> requesting =
ZenAccessController.getPackagesRequestingNotificationPolicyAccess();
if (!requesting.isEmpty()) {
final List<ApplicationInfo> installed = mPkgMan.getInstalledApplications(0);
if (installed != null) {
@@ -143,204 +121,42 @@ 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<String> getPackagesRequestingNotificationPolicyAccess() {
ArraySet<String> requestingPackages = new ArraySet<>();
try {
final String[] PERM = {
android.Manifest.permission.ACCESS_NOTIFICATION_POLICY
};
final ParceledListSlice list = AppGlobals.getPackageManager()
.getPackagesHoldingPermissions(PERM, 0 /*flags*/,
ActivityManager.getCurrentUser());
final List<PackageInfo> pkgs = list.getList();
if (pkgs != null) {
for (PackageInfo info : pkgs) {
requestingPackages.add(info.packageName);
}
}
} catch(RemoteException e) {
Log.e(TAG, "Cannot reach packagemanager", e);
}
return requestingPackages;
}
private boolean hasAccess(String pkg) {
return mNoMan.isNotificationPolicyAccessGrantedForPackage(pkg);
}
private static void setAccess(final Context context, final String pkg, final boolean access) {
logSpecialPermissionChange(access, pkg, context);
AsyncTask.execute(new Runnable() {
@Override
public void run() {
final NotificationManager mgr = context.getSystemService(NotificationManager.class);
mgr.setNotificationPolicyAccessGranted(pkg, access);
}
});
}
@VisibleForTesting
static void logSpecialPermissionChange(boolean enable, String packageName, Context context) {
int logCategory = enable ? SettingsEnums.APP_SPECIAL_PERMISSION_DND_ALLOW
: SettingsEnums.APP_SPECIAL_PERMISSION_DND_DENY;
FeatureFactory.getFactory(context).getMetricsFeatureProvider().action(context,
logCategory, packageName);
}
private static void deleteRules(final Context context, final String pkg) {
AsyncTask.execute(new Runnable() {
@Override
public void run() {
final NotificationManager mgr = context.getSystemService(NotificationManager.class);
mgr.removeAutomaticZenRules(pkg);
}
});
}
private final class SettingObserver extends ContentObserver {
public SettingObserver() {
super(new Handler(Looper.getMainLooper()));
}
@Override
public void onChange(boolean selfChange, Uri uri) {
reloadList();
}
}
/**
* Warning dialog when allowing zen access warning about the privileges being granted.
* @return the summary for the current state of whether the app associated with the given
* {@param packageName} is allowed to enter picture-in-picture.
*/
public static class ScaryWarningDialogFragment extends InstrumentedDialogFragment {
static final String KEY_PKG = "p";
static final String KEY_LABEL = "l";
@Override
public int getMetricsCategory() {
return SettingsEnums.DIALOG_ZEN_ACCESS_GRANT;
}
public ScaryWarningDialogFragment setPkgInfo(String pkg, CharSequence label) {
Bundle args = new Bundle();
args.putString(KEY_PKG, pkg);
args.putString(KEY_LABEL, TextUtils.isEmpty(label) ? pkg : label.toString());
setArguments(args);
return this;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Bundle args = getArguments();
final String pkg = args.getString(KEY_PKG);
final String label = args.getString(KEY_LABEL);
final String title = getResources().getString(R.string.zen_access_warning_dialog_title,
label);
final String summary = getResources()
.getString(R.string.zen_access_warning_dialog_summary);
return new AlertDialog.Builder(getContext())
.setMessage(summary)
.setTitle(title)
.setCancelable(true)
.setPositiveButton(R.string.allow,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
setAccess(getContext(), pkg, true);
}
})
.setNegativeButton(R.string.deny,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// pass
}
})
.create();
}
}
/**
* Warning dialog when revoking zen access warning that zen rule instances will be deleted.
*/
public static class FriendlyWarningDialogFragment extends InstrumentedDialogFragment {
static final String KEY_PKG = "p";
static final String KEY_LABEL = "l";
@Override
public int getMetricsCategory() {
return SettingsEnums.DIALOG_ZEN_ACCESS_REVOKE;
}
public FriendlyWarningDialogFragment setPkgInfo(String pkg, CharSequence label) {
Bundle args = new Bundle();
args.putString(KEY_PKG, pkg);
args.putString(KEY_LABEL, TextUtils.isEmpty(label) ? pkg : label.toString());
setArguments(args);
return this;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Bundle args = getArguments();
final String pkg = args.getString(KEY_PKG);
final String label = args.getString(KEY_LABEL);
final String title = getResources().getString(
R.string.zen_access_revoke_warning_dialog_title, label);
final String summary = getResources()
.getString(R.string.zen_access_revoke_warning_dialog_summary);
return new AlertDialog.Builder(getContext())
.setMessage(summary)
.setTitle(title)
.setCancelable(true)
.setPositiveButton(R.string.okay,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
deleteRules(getContext(), pkg);
setAccess(getContext(), pkg, false);
}
})
.setNegativeButton(R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// pass
}
})
.create();
}
private int getPreferenceSummary(String packageName) {
final boolean enabled = ZenAccessController.hasAccess(getContext(), packageName);
return enabled ? R.string.app_permission_summary_allowed
: R.string.app_permission_summary_not_allowed;
}
public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =

View File

@@ -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<Uri> 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);

View File

@@ -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<Uri, Class<? extends CustomSliceable>> 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));
}
}

View File

@@ -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);

View File

@@ -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();
}
}

View File

@@ -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<AbstractPreferenceController> controllers = new ArrayList<>();
final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
final boolean isDisplaySavedNetworkDetails =
getArguments().getBoolean(EXTRA_IS_SAVED_NETWORK, false /* defaultValue */);
if (isDisplaySavedNetworkDetails) {
mWifiDetailPreferenceController =
WifiDetailSavedNetworkPreferenceController.newInstance(
mAccessPoint,
cm,
context,
this,
new Handler(Looper.getMainLooper()), // UI thread.
getSettingsLifecycle(),
context.getSystemService(WifiManager.class),
mMetricsFeatureProvider);
} else {
mWifiDetailPreferenceController = WifiDetailPreferenceController.newInstance(
mAccessPoint,
cm,
context,
this,
new Handler(Looper.getMainLooper()), // UI thread.
getSettingsLifecycle(),
context.getSystemService(WifiManager.class),
mMetricsFeatureProvider);
}
mWifiDetailPreferenceController = WifiDetailPreferenceController.newInstance(
mAccessPoint,
cm,
context,
this,
new Handler(Looper.getMainLooper()), // UI thread.
getSettingsLifecycle(),
context.getSystemService(WifiManager.class),
mMetricsFeatureProvider);
controllers.add(mWifiDetailPreferenceController);
controllers.add(new WifiMeteredPreferenceController(context, mAccessPoint.getConfig()));

View File

@@ -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())

View File

@@ -91,4 +91,7 @@
<!-- Email address for the homepage contextual cards feedback -->
<string name="config_contextual_card_feedback_email" translatable="false">test@test.test</string>
<!-- Grayscale settings intent -->
<string name="config_grayscale_settings_intent" translate="false">intent:#Intent;action=test.test;end</string>
</resources>

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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<MediaDevice> 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);
}
}

View File

@@ -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<BluetoothDevice> 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));
}
}

View File

@@ -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"));
}
}

View File

@@ -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);
}

View File

@@ -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<String> 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);
}
}