Snap for 5389215 from e230d19810 to qt-release
Change-Id: Ifd21ff224d503a39b7e63d6705b21a9440b37490
This commit is contained in:
@@ -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"
|
||||
|
||||
20
res/color/circle_outline_color.xml
Normal file
20
res/color/circle_outline_color.xml
Normal 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>
|
||||
22
res/drawable/circle_outline.xml
Normal file
22
res/drawable/circle_outline.xml
Normal 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>
|
||||
25
res/drawable/ic_gray_scale_24dp.xml
Normal file
25
res/drawable/ic_gray_scale_24dp.xml
Normal 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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
28
res/layout/time_zone_search_header.xml
Normal file
28
res/layout/time_zone_search_header.xml
Normal 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"/>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 & 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 & 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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
27
res/xml/zen_access_permission_details.xml
Normal file
27
res/xml/zen_access_permission_details.xml
Normal 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>
|
||||
@@ -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 */ }
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 */);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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(),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
104
src/com/android/settings/media/MediaOutputIndicatorSlice.java
Normal file
104
src/com/android/settings/media/MediaOutputIndicatorSlice.java
Normal 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;
|
||||
}
|
||||
}
|
||||
161
src/com/android/settings/media/MediaOutputIndicatorWorker.java
Normal file
161
src/com/android/settings/media/MediaOutputIndicatorWorker.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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 =
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user