summaryrefslogtreecommitdiff
path: root/packages/SettingsLib/src
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2020-09-10 17:22:01 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2020-09-10 17:22:01 +0000
commit8ac6741e47c76bde065f868ea64d2f04541487b9 (patch)
tree1a679458fdbd8d370692d56791e2bf83acee35b5 /packages/SettingsLib/src
parent3de940cc40b1e3fdf8224e18a8308a16768cbfa8 (diff)
parentc64112eb974e9aa7638aead998f07a868acfb5a7 (diff)
Merge "Merge Android R"
Diffstat (limited to 'packages/SettingsLib/src')
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/NetworkPolicyEditor.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java20
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java17
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/Utils.java96
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java31
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/accounts/AuthenticatorHelper.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java82
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java65
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java29
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java12
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java98
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java97
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java32
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java11
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java14
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java97
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java43
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/lifecycle/Lifecycle.java5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnAttach.java10
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java78
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/display/BrightnessUtils.java46
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/drawable/CircleFramedDrawable.java26
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java23
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java147
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java7
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java43
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java347
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java78
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java493
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java537
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java287
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/MediaDeviceUtils.java14
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java73
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java27
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java119
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java48
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/net/UidDetailProvider.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java236
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java60
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java56
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/utils/ColorUtil.java35
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java82
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixin.java78
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixinCompat.java72
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/widget/UpdatableListPreferenceDialogFragment.java174
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java237
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/LongPressWifiEntryPreference.java46
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java25
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java307
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiSavedConfigUtils.java12
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java90
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java26
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java42
62 files changed, 3513 insertions, 1252 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/NetworkPolicyEditor.java b/packages/SettingsLib/src/com/android/settingslib/NetworkPolicyEditor.java
index b01fc8541957..f5aa652f3194 100644
--- a/packages/SettingsLib/src/com/android/settingslib/NetworkPolicyEditor.java
+++ b/packages/SettingsLib/src/com/android/settingslib/NetworkPolicyEditor.java
@@ -216,7 +216,7 @@ public class NetworkPolicyEditor {
private static NetworkTemplate buildUnquotedNetworkTemplate(NetworkTemplate template) {
if (template == null) return null;
final String networkId = template.getNetworkId();
- final String strippedNetworkId = WifiInfo.removeDoubleQuotes(networkId);
+ final String strippedNetworkId = WifiInfo.sanitizeSsid(networkId);
if (!TextUtils.equals(strippedNetworkId, networkId)) {
return new NetworkTemplate(
template.getMatchRule(), template.getSubscriberId(), strippedNetworkId);
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index 9a41f1d6a2b1..9f16d033aea5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -408,23 +408,6 @@ public class RestrictedLockUtilsInternal extends RestrictedLockUtils {
}
/**
- * Checks if {@link android.app.admin.DevicePolicyManager#setAutoTimeRequired} is enforced
- * on the device.
- *
- * @return EnforcedAdmin Object containing the device owner component and
- * userId the device owner is running as, or {@code null} setAutoTimeRequired is not enforced.
- */
- public static EnforcedAdmin checkIfAutoTimeRequired(Context context) {
- DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
- Context.DEVICE_POLICY_SERVICE);
- if (dpm == null || !dpm.getAutoTimeRequired()) {
- return null;
- }
- ComponentName adminComponent = dpm.getDeviceOwnerComponentOnCallingUser();
- return new EnforcedAdmin(adminComponent, getUserHandleOf(UserHandle.myUserId()));
- }
-
- /**
* Checks if an admin has enforced minimum password quality requirements on the given user.
*
* @return EnforcedAdmin Object containing the enforced admin component and admin user details,
@@ -663,7 +646,8 @@ public class RestrictedLockUtilsInternal extends RestrictedLockUtils {
final SpannableStringBuilder sb = new SpannableStringBuilder(textView.getText());
removeExistingRestrictedSpans(sb);
if (disabled) {
- final int disabledColor = context.getColor(R.color.disabled_text_color);
+ final int disabledColor = Utils.getDisabled(context,
+ textView.getCurrentTextColor());
sb.setSpan(new ForegroundColorSpan(disabledColor), 0, sb.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setCompoundDrawables(null, null, getRestrictedPadlock(context), null);
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index 0ed507c46372..5c05a1bd6722 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -24,6 +24,8 @@ import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.core.content.res.TypedArrayUtils;
@@ -39,6 +41,7 @@ public class RestrictedSwitchPreference extends SwitchPreference {
RestrictedPreferenceHelper mHelper;
boolean mUseAdditionalSummary = false;
CharSequence mRestrictedSwitchSummary;
+ private int mIconSize;
public RestrictedSwitchPreference(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
@@ -62,7 +65,7 @@ public class RestrictedSwitchPreference extends SwitchPreference {
&& restrictedSwitchSummary.type == TypedValue.TYPE_STRING) {
if (restrictedSwitchSummary.resourceId != 0) {
mRestrictedSwitchSummary =
- context.getText(restrictedSwitchSummary.resourceId);
+ context.getText(restrictedSwitchSummary.resourceId);
} else {
mRestrictedSwitchSummary = restrictedSwitchSummary.string;
}
@@ -87,6 +90,10 @@ public class RestrictedSwitchPreference extends SwitchPreference {
this(context, null);
}
+ public void setIconSize(int iconSize) {
+ mIconSize = iconSize;
+ }
+
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
@@ -95,7 +102,7 @@ public class RestrictedSwitchPreference extends SwitchPreference {
CharSequence switchSummary;
if (mRestrictedSwitchSummary == null) {
switchSummary = getContext().getText(isChecked()
- ? R.string.enabled_by_admin : R.string.disabled_by_admin);
+ ? R.string.enabled_by_admin : R.string.disabled_by_admin);
} else {
switchSummary = mRestrictedSwitchSummary;
}
@@ -109,6 +116,12 @@ public class RestrictedSwitchPreference extends SwitchPreference {
switchWidget.setVisibility(isDisabledByAdmin() ? View.GONE : View.VISIBLE);
}
+ final ImageView icon = holder.itemView.findViewById(android.R.id.icon);
+
+ if (mIconSize > 0) {
+ icon.setLayoutParams(new LinearLayout.LayoutParams(mIconSize, mIconSize));
+ }
+
if (mUseAdditionalSummary) {
final TextView additionalSummaryView = (TextView) holder.findViewById(
R.id.additional_summary);
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 1141daa94e6d..a43412e116c8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -3,6 +3,7 @@ package com.android.settingslib;
import android.annotation.ColorInt;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -13,6 +14,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.location.LocationManager;
import android.media.AudioManager;
@@ -29,14 +31,14 @@ import android.telephony.ServiceState;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.UserIcons;
+import com.android.launcher3.icons.IconFactory;
import com.android.settingslib.drawable.UserIconDrawable;
+import com.android.settingslib.fuelgauge.BatteryStatus;
import java.text.NumberFormat;
public class Utils {
- private static final String CURRENT_MODE_KEY = "CURRENT_MODE";
- private static final String NEW_MODE_KEY = "NEW_MODE";
@VisibleForTesting
static final String STORAGE_MANAGER_ENABLED_PROPERTY =
"ro.storage_manager.enabled";
@@ -56,24 +58,11 @@ public class Utils {
public static void updateLocationEnabled(Context context, boolean enabled, int userId,
int source) {
- LocationManager locationManager = context.getSystemService(LocationManager.class);
-
Settings.Secure.putIntForUser(
context.getContentResolver(), Settings.Secure.LOCATION_CHANGER, source,
userId);
- Intent intent = new Intent(LocationManager.MODE_CHANGING_ACTION);
- final int oldMode = locationManager.isLocationEnabled()
- ? Settings.Secure.LOCATION_MODE_ON
- : Settings.Secure.LOCATION_MODE_OFF;
- final int newMode = enabled
- ? Settings.Secure.LOCATION_MODE_ON
- : Settings.Secure.LOCATION_MODE_OFF;
- intent.putExtra(CURRENT_MODE_KEY, oldMode);
- intent.putExtra(NEW_MODE_KEY, newMode);
- context.sendBroadcastAsUser(
- intent, UserHandle.of(userId), android.Manifest.permission.WRITE_SECURE_SETTINGS);
-
+ LocationManager locationManager = context.getSystemService(LocationManager.class);
locationManager.setLocationEnabledForUser(enabled, UserHandle.of(userId));
}
@@ -132,7 +121,7 @@ public class Utils {
public static Drawable getUserIcon(Context context, UserManager um, UserInfo user) {
final int iconSize = UserIconDrawable.getSizeForList(context);
if (user.isManagedProfile()) {
- Drawable drawable = UserIconDrawable.getManagedUserDrawable(context);
+ Drawable drawable = UserIconDrawable.getManagedUserDrawable(context);
drawable.setBounds(0, 0, iconSize, iconSize);
return drawable;
}
@@ -174,20 +163,43 @@ public class Utils {
return (level * 100) / scale;
}
- public static String getBatteryStatus(Resources res, Intent batteryChangedIntent) {
- int status = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_STATUS,
+ /**
+ * Get battery status string
+ *
+ * @param context the context
+ * @param batteryChangedIntent battery broadcast intent received from {@link
+ * Intent.ACTION_BATTERY_CHANGED}.
+ * @return battery status string
+ */
+ public static String getBatteryStatus(Context context, Intent batteryChangedIntent) {
+ final int status = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_STATUS,
BatteryManager.BATTERY_STATUS_UNKNOWN);
- String statusString;
- if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
- statusString = res.getString(R.string.battery_info_status_charging);
- } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) {
- statusString = res.getString(R.string.battery_info_status_discharging);
- } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
- statusString = res.getString(R.string.battery_info_status_not_charging);
- } else if (status == BatteryManager.BATTERY_STATUS_FULL) {
+ final Resources res = context.getResources();
+
+ String statusString = res.getString(R.string.battery_info_status_unknown);
+ final BatteryStatus batteryStatus = new BatteryStatus(batteryChangedIntent);
+
+ if (batteryStatus.isCharged()) {
statusString = res.getString(R.string.battery_info_status_full);
} else {
- statusString = res.getString(R.string.battery_info_status_unknown);
+ if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
+ switch (batteryStatus.getChargingSpeed(context)) {
+ case BatteryStatus.CHARGING_FAST:
+ statusString = res.getString(R.string.battery_info_status_charging_fast);
+ break;
+ case BatteryStatus.CHARGING_SLOWLY:
+ statusString = res.getString(R.string.battery_info_status_charging_slow);
+ break;
+ default:
+ statusString = res.getString(R.string.battery_info_status_charging);
+ break;
+ }
+
+ } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) {
+ statusString = res.getString(R.string.battery_info_status_discharging);
+ } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
+ statusString = res.getString(R.string.battery_info_status_not_charging);
+ }
}
return statusString;
@@ -218,6 +230,13 @@ public class Utils {
return list.getDefaultColor();
}
+ /**
+ * This method computes disabled color from normal color
+ *
+ * @param context the context
+ * @param inputColor normal color.
+ * @return disabled color.
+ */
@ColorInt
public static int getDisabled(Context context, int inputColor) {
return applyAlphaAttr(context, android.R.attr.disabledAlpha, inputColor);
@@ -258,8 +277,12 @@ public class Utils {
}
public static int getThemeAttr(Context context, int attr) {
+ return getThemeAttr(context, attr, 0);
+ }
+
+ public static int getThemeAttr(Context context, int attr, int defaultValue) {
TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
- int theme = ta.getResourceId(0, 0);
+ int theme = ta.getResourceId(0, defaultValue);
ta.recycle();
return theme;
}
@@ -432,6 +455,21 @@ public class Utils {
return state;
}
+ /** Get the corresponding adaptive icon drawable. */
+ public static Drawable getBadgedIcon(Context context, Drawable icon, UserHandle user) {
+ try (IconFactory iconFactory = IconFactory.obtain(context)) {
+ final Bitmap iconBmp = iconFactory.createBadgedIconBitmap(icon, user,
+ true /* shrinkNonAdaptiveIcons */).icon;
+ return new BitmapDrawable(context.getResources(), iconBmp);
+ }
+ }
+
+ /** Get the {@link Drawable} that represents the app icon */
+ public static Drawable getBadgedIcon(Context context, ApplicationInfo appInfo) {
+ return getBadgedIcon(context, appInfo.loadUnbadgedIcon(context.getPackageManager()),
+ UserHandle.getUserHandleForUid(appInfo.uid));
+ }
+
private static boolean isNotInIwlan(ServiceState serviceState) {
final NetworkRegistrationInfo networkRegWlan = serviceState.getNetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_PS,
diff --git a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
index a18600abf788..59735f413f9d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
@@ -48,11 +48,21 @@ public class AccessibilityUtils {
return getEnabledServicesFromSettings(context, UserHandle.myUserId());
}
+ /**
+ * Check if the accessibility service is crashed
+ *
+ * @param packageName The package name to check
+ * @param serviceName The service name to check
+ * @param installedServiceInfos The list of installed accessibility service
+ * @return {@code true} if the accessibility service is crashed for the user.
+ * {@code false} otherwise.
+ */
public static boolean hasServiceCrashed(String packageName, String serviceName,
- List<AccessibilityServiceInfo> enabledServiceInfos) {
- for (int i = 0; i < enabledServiceInfos.size(); i++) {
- AccessibilityServiceInfo accessibilityServiceInfo = enabledServiceInfos.get(i);
- final ServiceInfo serviceInfo = enabledServiceInfos.get(i).getResolveInfo().serviceInfo;
+ List<AccessibilityServiceInfo> installedServiceInfos) {
+ for (int i = 0; i < installedServiceInfos.size(); i++) {
+ final AccessibilityServiceInfo accessibilityServiceInfo = installedServiceInfos.get(i);
+ final ServiceInfo serviceInfo =
+ installedServiceInfos.get(i).getResolveInfo().serviceInfo;
if (TextUtils.equals(serviceInfo.packageName, packageName)
&& TextUtils.equals(serviceInfo.name, serviceName)) {
return accessibilityServiceInfo.crashed;
@@ -179,19 +189,6 @@ public class AccessibilityUtils {
return context.getString(R.string.config_defaultAccessibilityService);
}
- /**
- * Check if the accessibility shortcut is enabled for a user
- *
- * @param context A valid context
- * @param userId The user of interest
- * @return {@code true} if the shortcut is enabled for the user. {@code false} otherwise.
- * Note that the shortcut may be enabled, but no action associated with it.
- */
- public static boolean isShortcutEnabled(Context context, int userId) {
- return Settings.Secure.getIntForUser(context.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 1, userId) == 1;
- }
-
private static Set<ComponentName> getInstalledServices(Context context) {
final Set<ComponentName> installedServices = new HashSet<>();
installedServices.clear();
diff --git a/packages/SettingsLib/src/com/android/settingslib/accounts/AuthenticatorHelper.java b/packages/SettingsLib/src/com/android/settingslib/accounts/AuthenticatorHelper.java
index ef511bbc8133..4af9e3c441de 100644
--- a/packages/SettingsLib/src/com/android/settingslib/accounts/AuthenticatorHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/accounts/AuthenticatorHelper.java
@@ -32,6 +32,8 @@ import android.os.AsyncTask;
import android.os.UserHandle;
import android.util.Log;
+import com.android.settingslib.Utils;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
@@ -116,7 +118,7 @@ final public class AuthenticatorHelper extends BroadcastReceiver {
if (icon == null) {
icon = context.getPackageManager().getDefaultActivityIcon();
}
- return icon;
+ return Utils.getBadgedIcon(mContext, icon, mUserHandle);
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
index c4ff71940d20..898796828131 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
@@ -19,10 +19,15 @@ package com.android.settingslib.applications;
import android.app.Application;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.hardware.usb.IUsbManager;
+import android.net.Uri;
+import android.os.Environment;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -44,6 +49,15 @@ public class AppUtils {
*/
private static InstantAppDataProvider sInstantAppDataProvider = null;
+ private static final Intent sBrowserIntent;
+
+ static {
+ sBrowserIntent = new Intent()
+ .setAction(Intent.ACTION_VIEW)
+ .addCategory(Intent.CATEGORY_BROWSABLE)
+ .setData(Uri.parse("http:"));
+ }
+
public static CharSequence getLaunchByDefaultSummary(ApplicationsState.AppEntry appEntry,
IUsbManager usbManager, PackageManager pm, Context context) {
String packageName = appEntry.info.packageName;
@@ -111,17 +125,8 @@ public class AppUtils {
/** Returns the label for a given package. */
public static CharSequence getApplicationLabel(
PackageManager packageManager, String packageName) {
- try {
- final ApplicationInfo appInfo =
- packageManager.getApplicationInfo(
- packageName,
- PackageManager.MATCH_DISABLED_COMPONENTS
- | PackageManager.MATCH_ANY_USER);
- return appInfo.loadLabel(packageManager);
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Unable to find info for package: " + packageName);
- }
- return null;
+ return com.android.settingslib.utils.applications.AppUtils
+ .getApplicationLabel(packageManager, packageName);
}
/**
@@ -129,7 +134,7 @@ public class AppUtils {
*/
public static boolean isHiddenSystemModule(Context context, String packageName) {
return ApplicationsState.getInstance((Application) context.getApplicationContext())
- .isHiddenModule(packageName);
+ .isHiddenModule(packageName);
}
/**
@@ -140,4 +145,57 @@ public class AppUtils {
.isSystemModule(packageName);
}
+ /**
+ * Returns a boolean indicating whether a given package is a mainline module.
+ */
+ public static boolean isMainlineModule(PackageManager pm, String packageName) {
+ // Check if the package is listed among the system modules.
+ try {
+ pm.getModuleInfo(packageName, 0 /* flags */);
+ return true;
+ } catch (PackageManager.NameNotFoundException e) {
+ //pass
+ }
+
+ try {
+ final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */);
+ // Check if the package is contained in an APEX. There is no public API to properly
+ // check whether a given APK package comes from an APEX registered as module.
+ // Therefore we conservatively assume that any package scanned from an /apex path is
+ // a system package.
+ return pkg.applicationInfo.sourceDir.startsWith(
+ Environment.getApexDirectory().getAbsolutePath());
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Returns a content description of an app name which distinguishes a personal app from a
+ * work app for accessibility purpose.
+ * If the app is in a work profile, then add a "work" prefix to the app name.
+ */
+ public static String getAppContentDescription(Context context, String packageName,
+ int userId) {
+ return com.android.settingslib.utils.applications.AppUtils.getAppContentDescription(context,
+ packageName, userId);
+ }
+
+ /**
+ * Returns a boolean indicating whether a given package is a browser app.
+ *
+ * An app is a "browser" if it has an activity resolution that wound up
+ * marked with the 'handleAllWebDataURI' flag.
+ */
+ public static boolean isBrowserApp(Context context, String packageName, int userId) {
+ sBrowserIntent.setPackage(packageName);
+ final List<ResolveInfo> list = context.getPackageManager().queryIntentActivitiesAsUser(
+ sBrowserIntent, PackageManager.MATCH_ALL, userId);
+ for (ResolveInfo info : list) {
+ if (info.activityInfo != null && info.handleAllWebDataURI) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index f9df5a3eef28..a89cf37e2d06 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -59,6 +59,8 @@ import androidx.lifecycle.OnLifecycleEvent;
import com.android.internal.R;
import com.android.internal.util.ArrayUtils;
+import com.android.settingslib.Utils;
+import com.android.settingslib.utils.ThreadUtils;
import java.io.File;
import java.io.IOException;
@@ -199,8 +201,7 @@ public class ApplicationsState {
mEntriesMap.put(userId, new HashMap<>());
}
- mThread = new HandlerThread("ApplicationsState.Loader",
- Process.THREAD_PRIORITY_BACKGROUND);
+ mThread = new HandlerThread("ApplicationsState.Loader");
mThread.start();
mBackgroundHandler = new BackgroundHandler(mThread.getLooper());
@@ -496,7 +497,21 @@ public class ApplicationsState {
return;
}
synchronized (entry) {
- entry.ensureIconLocked(mContext, mDrawableFactory);
+ entry.ensureIconLocked(mContext);
+ }
+ }
+
+ /**
+ * To generate and cache the label description.
+ *
+ * @param entry contain the entries of an app
+ */
+ public void ensureLabelDescription(AppEntry entry) {
+ if (entry.labelDescription != null) {
+ return;
+ }
+ synchronized (entry) {
+ entry.ensureLabelDescriptionLocked(mContext);
}
}
@@ -866,6 +881,10 @@ public class ApplicationsState {
void handleRebuildList() {
AppFilter filter;
Comparator<AppEntry> comparator;
+
+ if (!mResumed) {
+ return;
+ }
synchronized (mRebuildSync) {
if (!mRebuildRequested) {
return;
@@ -1070,8 +1089,8 @@ public class ApplicationsState {
}
}
if (rebuildingSessions != null) {
- for (int i = 0; i < rebuildingSessions.size(); i++) {
- rebuildingSessions.get(i).handleRebuildList();
+ for (Session session : rebuildingSessions) {
+ session.handleRebuildList();
}
}
@@ -1213,7 +1232,7 @@ public class ApplicationsState {
AppEntry entry = mAppEntries.get(i);
if (entry.icon == null || !entry.mounted) {
synchronized (entry) {
- if (entry.ensureIconLocked(mContext, mDrawableFactory)) {
+ if (entry.ensureIconLocked(mContext)) {
if (!mRunning) {
mRunning = true;
Message m = mMainHandler.obtainMessage(
@@ -1520,6 +1539,7 @@ public class ApplicationsState {
public long size;
public long internalSize;
public long externalSize;
+ public String labelDescription;
public boolean mounted;
@@ -1569,6 +1589,15 @@ public class ApplicationsState {
this.size = SIZE_UNKNOWN;
this.sizeStale = true;
ensureLabel(context);
+ // Speed up the cache of the icon and label description if they haven't been created.
+ ThreadUtils.postOnBackgroundThread(() -> {
+ if (this.icon == null) {
+ this.ensureIconLocked(context);
+ }
+ if (this.labelDescription == null) {
+ this.ensureLabelDescriptionLocked(context);
+ }
+ });
}
public void ensureLabel(Context context) {
@@ -1584,10 +1613,10 @@ public class ApplicationsState {
}
}
- boolean ensureIconLocked(Context context, IconDrawableFactory drawableFactory) {
+ boolean ensureIconLocked(Context context) {
if (this.icon == null) {
if (this.apkFile.exists()) {
- this.icon = drawableFactory.getBadgedIcon(info);
+ this.icon = Utils.getBadgedIcon(context, info);
return true;
} else {
this.mounted = false;
@@ -1598,7 +1627,7 @@ public class ApplicationsState {
// its icon.
if (this.apkFile.exists()) {
this.mounted = true;
- this.icon = drawableFactory.getBadgedIcon(info);
+ this.icon = Utils.getBadgedIcon(context, info);
return true;
}
}
@@ -1612,6 +1641,24 @@ public class ApplicationsState {
return "";
}
}
+
+ /**
+ * Get the label description which distinguishes a personal app from a work app for
+ * accessibility purpose. If the app is in a work profile, then add a "work" prefix to the
+ * app label.
+ *
+ * @param context The application context
+ */
+ public void ensureLabelDescriptionLocked(Context context) {
+ final int userId = UserHandle.getUserId(this.info.uid);
+ if (UserManager.get(context).isManagedProfile(userId)) {
+ this.labelDescription = context.getString(
+ com.android.settingslib.R.string.accessibility_work_profile_app_description,
+ this.label);
+ } else {
+ this.labelDescription = this.label;
+ }
+ }
}
private static boolean hasFlag(int flags, int flag) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java
index 454d1dce0b2f..bd9e760acfda 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java
@@ -47,6 +47,7 @@ public class ServiceListing {
private final String mIntentAction;
private final String mPermission;
private final String mNoun;
+ private final boolean mAddDeviceLockedFlags;
private final HashSet<ComponentName> mEnabledServices = new HashSet<>();
private final List<ServiceInfo> mServices = new ArrayList<>();
private final List<Callback> mCallbacks = new ArrayList<>();
@@ -54,7 +55,8 @@ public class ServiceListing {
private boolean mListening;
private ServiceListing(Context context, String tag,
- String setting, String intentAction, String permission, String noun) {
+ String setting, String intentAction, String permission, String noun,
+ boolean addDeviceLockedFlags) {
mContentResolver = context.getContentResolver();
mContext = context;
mTag = tag;
@@ -62,6 +64,7 @@ public class ServiceListing {
mIntentAction = intentAction;
mPermission = permission;
mNoun = noun;
+ mAddDeviceLockedFlags = addDeviceLockedFlags;
}
public void addCallback(Callback callback) {
@@ -125,11 +128,15 @@ public class ServiceListing {
mServices.clear();
final int user = ActivityManager.getCurrentUser();
+ int flags = PackageManager.GET_SERVICES | PackageManager.GET_META_DATA;
+ if (mAddDeviceLockedFlags) {
+ flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+ }
+
final PackageManager pmWrapper = mContext.getPackageManager();
List<ResolveInfo> installedServices = pmWrapper.queryIntentServicesAsUser(
- new Intent(mIntentAction),
- PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
- user);
+ new Intent(mIntentAction), flags, user);
for (ResolveInfo resolveInfo : installedServices) {
ServiceInfo info = resolveInfo.serviceInfo;
@@ -186,6 +193,7 @@ public class ServiceListing {
private String mIntentAction;
private String mPermission;
private String mNoun;
+ private boolean mAddDeviceLockedFlags = false;
public Builder(Context context) {
mContext = context;
@@ -216,8 +224,19 @@ public class ServiceListing {
return this;
}
+ /**
+ * Set to true to add support for both MATCH_DIRECT_BOOT_AWARE and
+ * MATCH_DIRECT_BOOT_UNAWARE flags when querying PackageManager. Required to get results
+ * prior to the user unlocking the device for the first time.
+ */
+ public Builder setAddDeviceLockedFlags(boolean addDeviceLockedFlags) {
+ mAddDeviceLockedFlags = addDeviceLockedFlags;
+ return this;
+ }
+
public ServiceListing build() {
- return new ServiceListing(mContext, mTag, mSetting, mIntentAction, mPermission, mNoun);
+ return new ServiceListing(mContext, mTag, mSetting, mIntentAction, mPermission, mNoun,
+ mAddDeviceLockedFlags);
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 29015c9a9f68..59d8acb82196 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -212,15 +212,21 @@ public class BluetoothEventManager {
}
private void dispatchAudioModeChanged() {
- mDeviceManager.dispatchAudioModeChanged();
+ for (CachedBluetoothDevice cachedDevice : mDeviceManager.getCachedDevicesCopy()) {
+ cachedDevice.onAudioModeChanged();
+ }
for (BluetoothCallback callback : mCallbacks) {
callback.onAudioModeChanged();
}
}
- private void dispatchActiveDeviceChanged(CachedBluetoothDevice activeDevice,
+ @VisibleForTesting
+ void dispatchActiveDeviceChanged(CachedBluetoothDevice activeDevice,
int bluetoothProfile) {
- mDeviceManager.onActiveDeviceChanged(activeDevice, bluetoothProfile);
+ for (CachedBluetoothDevice cachedDevice : mDeviceManager.getCachedDevicesCopy()) {
+ boolean isActive = Objects.equals(cachedDevice, activeDevice);
+ cachedDevice.onActiveDeviceChanged(isActive, bluetoothProfile);
+ }
for (BluetoothCallback callback : mCallbacks) {
callback.onActiveDeviceChanged(activeDevice, bluetoothProfile);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 2f34b2bad0ad..95e916b9871a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -1,5 +1,7 @@
package com.android.settingslib.bluetooth;
+import static com.android.settingslib.widget.AdaptiveOutlineDrawable.AdaptiveOutlineIconType.TYPE_ADVANCED;
+
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
@@ -7,6 +9,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.MediaStore;
@@ -132,6 +136,44 @@ public class BluetoothUtils {
*/
public static Pair<Drawable, String> getBtRainbowDrawableWithDescription(Context context,
CachedBluetoothDevice cachedDevice) {
+ final Resources resources = context.getResources();
+ final Pair<Drawable, String> pair = BluetoothUtils.getBtDrawableWithDescription(context,
+ cachedDevice);
+
+ if (pair.first instanceof BitmapDrawable) {
+ return new Pair<>(new AdaptiveOutlineDrawable(
+ resources, ((BitmapDrawable) pair.first).getBitmap()), pair.second);
+ }
+
+ return new Pair<>(buildBtRainbowDrawable(context,
+ pair.first, cachedDevice.getAddress().hashCode()), pair.second);
+ }
+
+ /**
+ * Build Bluetooth device icon with rainbow
+ */
+ public static Drawable buildBtRainbowDrawable(Context context, Drawable drawable,
+ int hashCode) {
+ final Resources resources = context.getResources();
+
+ // Deal with normal headset
+ final int[] iconFgColors = resources.getIntArray(R.array.bt_icon_fg_colors);
+ final int[] iconBgColors = resources.getIntArray(R.array.bt_icon_bg_colors);
+
+ // get color index based on mac address
+ final int index = Math.abs(hashCode % iconBgColors.length);
+ drawable.setTint(iconFgColors[index]);
+ final Drawable adaptiveIcon = new AdaptiveIcon(context, drawable);
+ ((AdaptiveIcon) adaptiveIcon).setBackgroundColor(iconBgColors[index]);
+
+ return adaptiveIcon;
+ }
+
+ /**
+ * Get bluetooth icon with description
+ */
+ public static Pair<Drawable, String> getBtDrawableWithDescription(Context context,
+ CachedBluetoothDevice cachedDevice) {
final Pair<Drawable, String> pair = BluetoothUtils.getBtClassDrawableWithDescription(
context, cachedDevice);
final BluetoothDevice bluetoothDevice = cachedDevice.getDevice();
@@ -150,7 +192,7 @@ public class BluetoothUtils {
context.getContentResolver().takePersistableUriPermission(iconUri,
Intent.FLAG_GRANT_READ_URI_PERMISSION);
} catch (SecurityException e) {
- Log.e(TAG, "Failed to take persistable permission for: " + iconUri);
+ Log.e(TAG, "Failed to take persistable permission for: " + iconUri, e);
}
try {
final Bitmap bitmap = MediaStore.Images.Media.getBitmap(
@@ -159,38 +201,58 @@ public class BluetoothUtils {
final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize,
iconSize, false);
bitmap.recycle();
- final AdaptiveOutlineDrawable drawable = new AdaptiveOutlineDrawable(
- resources, resizedBitmap);
- return new Pair<>(drawable, pair.second);
+ return new Pair<>(new BitmapDrawable(resources,
+ resizedBitmap), pair.second);
}
} catch (IOException e) {
Log.e(TAG, "Failed to get drawable for: " + iconUri, e);
+ } catch (SecurityException e) {
+ Log.e(TAG, "Failed to get permission for: " + iconUri, e);
}
}
}
- return new Pair<>(buildBtRainbowDrawable(context,
- pair.first, cachedDevice.getAddress().hashCode()), pair.second);
+ return new Pair<>(pair.first, pair.second);
}
/**
- * Build Bluetooth device icon with rainbow
+ * Build device icon with advanced outline
*/
- public static Drawable buildBtRainbowDrawable(Context context, Drawable drawable,
- int hashCode) {
+ public static Drawable buildAdvancedDrawable(Context context, Drawable drawable) {
+ final int iconSize = context.getResources().getDimensionPixelSize(
+ R.dimen.advanced_icon_size);
final Resources resources = context.getResources();
- // Deal with normal headset
- final int[] iconFgColors = resources.getIntArray(R.array.bt_icon_fg_colors);
- final int[] iconBgColors = resources.getIntArray(R.array.bt_icon_bg_colors);
+ Bitmap bitmap = null;
+ if (drawable instanceof BitmapDrawable) {
+ bitmap = ((BitmapDrawable) drawable).getBitmap();
+ } else {
+ final int width = drawable.getIntrinsicWidth();
+ final int height = drawable.getIntrinsicHeight();
+ bitmap = createBitmap(drawable,
+ width > 0 ? width : 1,
+ height > 0 ? height : 1);
+ }
- // get color index based on mac address
- final int index = Math.abs(hashCode % iconBgColors.length);
- drawable.setTint(iconFgColors[index]);
- final Drawable adaptiveIcon = new AdaptiveIcon(context, drawable);
- ((AdaptiveIcon) adaptiveIcon).setBackgroundColor(iconBgColors[index]);
+ if (bitmap != null) {
+ final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize,
+ iconSize, false);
+ bitmap.recycle();
+ return new AdaptiveOutlineDrawable(resources, resizedBitmap, TYPE_ADVANCED);
+ }
- return adaptiveIcon;
+ return drawable;
+ }
+
+ /**
+ * Creates a drawable with specified width and height.
+ */
+ public static Bitmap createBitmap(Drawable drawable, int width, int height) {
+ final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+ return bitmap;
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index f694f0e2a83d..4c80b91f300d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -24,6 +24,9 @@ import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.content.Context;
import android.content.SharedPreferences;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.os.ParcelUuid;
import android.os.SystemClock;
import android.text.TextUtils;
@@ -38,7 +41,6 @@ import com.android.settingslib.Utils;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -56,6 +58,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
// Some Hearing Aids (especially the 2nd device) needs more time to do service discovery
private static final long MAX_HEARING_AIDS_DELAY_FOR_AUTO_CONNECT = 15000;
private static final long MAX_HOGP_DELAY_FOR_AUTO_CONNECT = 30000;
+ private static final long MAX_MEDIA_PROFILE_CONNECT_DELAY = 60000;
private final Context mContext;
private final BluetoothAdapter mLocalAdapter;
@@ -67,10 +70,10 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
short mRssi;
// mProfiles and mRemovedProfiles does not do swap() between main and sub device. It is
// because current sub device is only for HearingAid and its profile is the same.
- private final List<LocalBluetoothProfile> mProfiles = new ArrayList<>();
+ private final Collection<LocalBluetoothProfile> mProfiles = new CopyOnWriteArrayList<>();
// List of profiles that were previously in mProfiles, but have been removed
- private final List<LocalBluetoothProfile> mRemovedProfiles = new ArrayList<>();
+ private final Collection<LocalBluetoothProfile> mRemovedProfiles = new CopyOnWriteArrayList<>();
// Device supports PANU but not NAP: remove PanProfile after device disconnects from NAP
private boolean mLocalNapRoleConnected;
@@ -91,9 +94,35 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
private boolean mIsActiveDeviceA2dp = false;
private boolean mIsActiveDeviceHeadset = false;
private boolean mIsActiveDeviceHearingAid = false;
+ // Media profile connect state
+ private boolean mIsA2dpProfileConnectedFail = false;
+ private boolean mIsHeadsetProfileConnectedFail = false;
+ private boolean mIsHearingAidProfileConnectedFail = false;
// Group second device for Hearing Aid
private CachedBluetoothDevice mSubDevice;
+ private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case BluetoothProfile.A2DP:
+ mIsA2dpProfileConnectedFail = true;
+ break;
+ case BluetoothProfile.HEADSET:
+ mIsHeadsetProfileConnectedFail = true;
+ break;
+ case BluetoothProfile.HEARING_AID:
+ mIsHearingAidProfileConnectedFail = true;
+ break;
+ default:
+ Log.w(TAG, "handleMessage(): unknown message : " + msg.what);
+ break;
+ }
+ Log.w(TAG, "Connect to profile : " + msg.what + " timeout, show error message !");
+ refresh();
+ }
+ };
+
CachedBluetoothDevice(Context context, LocalBluetoothProfileManager profileManager,
BluetoothDevice device) {
mContext = context;
@@ -134,6 +163,35 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
}
synchronized (mProfileLock) {
+ if (profile instanceof A2dpProfile || profile instanceof HeadsetProfile
+ || profile instanceof HearingAidProfile) {
+ setProfileConnectedStatus(profile.getProfileId(), false);
+ switch (newProfileState) {
+ case BluetoothProfile.STATE_CONNECTED:
+ mHandler.removeMessages(profile.getProfileId());
+ break;
+ case BluetoothProfile.STATE_CONNECTING:
+ mHandler.sendEmptyMessageDelayed(profile.getProfileId(),
+ MAX_MEDIA_PROFILE_CONNECT_DELAY);
+ break;
+ case BluetoothProfile.STATE_DISCONNECTING:
+ if (mHandler.hasMessages(profile.getProfileId())) {
+ mHandler.removeMessages(profile.getProfileId());
+ }
+ break;
+ case BluetoothProfile.STATE_DISCONNECTED:
+ if (mHandler.hasMessages(profile.getProfileId())) {
+ mHandler.removeMessages(profile.getProfileId());
+ setProfileConnectedStatus(profile.getProfileId(), true);
+ }
+ break;
+ default:
+ Log.w(TAG, "onProfileStateChanged(): unknown profile state : "
+ + newProfileState);
+ break;
+ }
+ }
+
if (newProfileState == BluetoothProfile.STATE_CONNECTED) {
if (profile instanceof MapProfile) {
profile.setEnabled(mDevice, true);
@@ -163,6 +221,24 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
fetchActiveDevices();
}
+ @VisibleForTesting
+ void setProfileConnectedStatus(int profileId, boolean isFailed) {
+ switch (profileId) {
+ case BluetoothProfile.A2DP:
+ mIsA2dpProfileConnectedFail = isFailed;
+ break;
+ case BluetoothProfile.HEADSET:
+ mIsHeadsetProfileConnectedFail = isFailed;
+ break;
+ case BluetoothProfile.HEARING_AID:
+ mIsHearingAidProfileConnectedFail = isFailed;
+ break;
+ default:
+ Log.w(TAG, "setProfileConnectedStatus(): unknown profile id : " + profileId);
+ break;
+ }
+ }
+
public void disconnect() {
synchronized (mProfileLock) {
mLocalAdapter.disconnectAllEnabledProfiles(mDevice);
@@ -639,10 +715,6 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
}
public List<LocalBluetoothProfile> getProfiles() {
- return Collections.unmodifiableList(mProfiles);
- }
-
- public List<LocalBluetoothProfile> getProfileListCopy() {
return new ArrayList<>(mProfiles);
}
@@ -660,7 +732,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
}
public List<LocalBluetoothProfile> getRemovedProfiles() {
- return mRemovedProfiles;
+ return new ArrayList<>(mRemovedProfiles);
}
public void registerCallback(Callback callback) {
@@ -828,6 +900,10 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
int leftBattery = -1;
int rightBattery = -1;
+ if (isProfileConnectedFail() && isConnected()) {
+ return mContext.getString(R.string.profile_connect_timeout_subtext);
+ }
+
synchronized (mProfileLock) {
for (LocalBluetoothProfile profile : getProfiles()) {
int connectionStatus = getProfileConnectionState(profile);
@@ -927,6 +1003,11 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
return leftBattery >= 0 && rightBattery >= 0;
}
+ private boolean isProfileConnectedFail() {
+ return mIsA2dpProfileConnectedFail || mIsHearingAidProfileConnectedFail
+ || mIsHeadsetProfileConnectedFail;
+ }
+
/**
* @return resource for android auto string that describes the connection state of this device.
*/
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 7050db14bfb1..cca9cfac2d22 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -26,7 +26,6 @@ import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
-import java.util.Objects;
/**
* CachedBluetoothDeviceManager manages the set of remote Bluetooth devices.
@@ -97,14 +96,17 @@ public class CachedBluetoothDeviceManager {
* @return the newly created CachedBluetoothDevice object
*/
public CachedBluetoothDevice addDevice(BluetoothDevice device) {
- LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
- CachedBluetoothDevice newDevice = new CachedBluetoothDevice(mContext, profileManager,
- device);
- mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(newDevice);
+ CachedBluetoothDevice newDevice;
+ final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
synchronized (this) {
- if (!mHearingAidDeviceManager.setSubDeviceIfNeeded(newDevice)) {
- mCachedDevices.add(newDevice);
- mBtManager.getEventManager().dispatchDeviceAdded(newDevice);
+ newDevice = findDevice(device);
+ if (newDevice == null) {
+ newDevice = new CachedBluetoothDevice(mContext, profileManager, device);
+ mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(newDevice);
+ if (!mHearingAidDeviceManager.setSubDeviceIfNeeded(newDevice)) {
+ mCachedDevices.add(newDevice);
+ mBtManager.getEventManager().dispatchDeviceAdded(newDevice);
+ }
}
}
@@ -226,14 +228,6 @@ public class CachedBluetoothDeviceManager {
}
}
- public synchronized void onActiveDeviceChanged(CachedBluetoothDevice activeDevice,
- int bluetoothProfile) {
- for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
- boolean isActive = Objects.equals(cachedDevice, activeDevice);
- cachedDevice.onActiveDeviceChanged(isActive, bluetoothProfile);
- }
- }
-
public synchronized boolean onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice
cachedDevice, int state) {
return mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice,
@@ -254,12 +248,6 @@ public class CachedBluetoothDeviceManager {
}
}
- public synchronized void dispatchAudioModeChanged() {
- for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
- cachedDevice.onAudioModeChanged();
- }
- }
-
private void log(String msg) {
if (DEBUG) {
Log.d(TAG, msg);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index c72efb7eec83..35bbbc0e8b39 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -48,6 +48,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
@@ -232,7 +233,7 @@ public class LocalBluetoothProfileManager {
}
private final Collection<ServiceListener> mServiceListeners =
- new ArrayList<ServiceListener>();
+ new CopyOnWriteArrayList<ServiceListener>();
private void addProfile(LocalBluetoothProfile profile,
String profileName, String stateChangedAction) {
@@ -361,14 +362,18 @@ public class LocalBluetoothProfileManager {
// not synchronized: use only from UI thread! (TODO: verify)
void callServiceConnectedListeners() {
- for (ServiceListener l : mServiceListeners) {
+ final Collection<ServiceListener> listeners = new ArrayList<>(mServiceListeners);
+
+ for (ServiceListener l : listeners) {
l.onServiceConnected();
}
}
// not synchronized: use only from UI thread! (TODO: verify)
void callServiceDisconnectedListeners() {
- for (ServiceListener listener : mServiceListeners) {
+ final Collection<ServiceListener> listeners = new ArrayList<>(mServiceListeners);
+
+ for (ServiceListener listener : listeners) {
listener.onServiceDisconnected();
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
index e8245082f8ef..d84e57a38ee4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
@@ -30,16 +30,22 @@ import com.android.internal.logging.nano.MetricsProto;
public class EventLogWriter implements LogWriter {
@Override
- public void visible(Context context, int source, int category) {
+ public void visible(Context context, int source, int category, int latency) {
final LogMaker logMaker = new LogMaker(category)
.setType(MetricsProto.MetricsEvent.TYPE_OPEN)
- .addTaggedData(MetricsProto.MetricsEvent.FIELD_CONTEXT, source);
+ .addTaggedData(MetricsProto.MetricsEvent.FIELD_CONTEXT, source)
+ .addTaggedData(MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE,
+ latency);
MetricsLogger.action(logMaker);
}
@Override
- public void hidden(Context context, int category) {
- MetricsLogger.hidden(context, category);
+ public void hidden(Context context, int category, int visibleTime) {
+ final LogMaker logMaker = new LogMaker(category)
+ .setType(MetricsProto.MetricsEvent.TYPE_CLOSE)
+ .addTaggedData(MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE,
+ visibleTime);
+ MetricsLogger.action(logMaker);
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
index f1876883a336..d4ef3d7b24a2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
@@ -26,12 +26,12 @@ public interface LogWriter {
/**
* Logs a visibility event when view becomes visible.
*/
- void visible(Context context, int source, int category);
+ void visible(Context context, int source, int category, int latency);
/**
* Logs a visibility event when view becomes hidden.
*/
- void hidden(Context context, int category);
+ void hidden(Context context, int category, int visibleTime);
/**
* Logs an user action.
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
index 8cc3b5a3f37a..bd0b9e93b09d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
@@ -23,6 +23,9 @@ import android.content.Intent;
import android.text.TextUtils;
import android.util.Pair;
+import androidx.annotation.NonNull;
+import androidx.preference.Preference;
+
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import java.util.ArrayList;
@@ -67,15 +70,28 @@ public class MetricsFeatureProvider {
SettingsEnums.PAGE_UNKNOWN);
}
- public void visible(Context context, int source, int category) {
+ /**
+ * Logs an event when target page is visible.
+ *
+ * @param source from this page id to target page
+ * @param category the target page id
+ * @param latency the latency of target page creation
+ */
+ public void visible(Context context, int source, int category, int latency) {
for (LogWriter writer : mLoggerWriters) {
- writer.visible(context, source, category);
+ writer.visible(context, source, category, latency);
}
}
- public void hidden(Context context, int category) {
+ /**
+ * Logs an event when target page is hidden.
+ *
+ * @param category the target page id
+ * @param visibleTime the time spending on target page since being visible
+ */
+ public void hidden(Context context, int category, int visibleTime) {
for (LogWriter writer : mLoggerWriters) {
- writer.hidden(context, category);
+ writer.hidden(context, category, visibleTime);
}
}
@@ -125,34 +141,65 @@ public class MetricsFeatureProvider {
return ((Instrumentable) object).getMetricsCategory();
}
- public void logDashboardStartIntent(Context context, Intent intent,
- int sourceMetricsCategory) {
+ /**
+ * Logs an event when the preference is clicked.
+ *
+ * @return true if the preference is loggable, otherwise false
+ */
+ public boolean logClickedPreference(@NonNull Preference preference, int sourceMetricsCategory) {
+ if (preference == null) {
+ return false;
+ }
+ return logSettingsTileClick(preference.getKey(), sourceMetricsCategory)
+ || logStartedIntent(preference.getIntent(), sourceMetricsCategory)
+ || logSettingsTileClick(preference.getFragment(), sourceMetricsCategory);
+ }
+
+ /**
+ * Logs an event when the intent is started.
+ *
+ * @return true if the intent is loggable, otherwise false
+ */
+ public boolean logStartedIntent(Intent intent, int sourceMetricsCategory) {
+ if (intent == null) {
+ return false;
+ }
+ final ComponentName cn = intent.getComponent();
+ return logSettingsTileClick(cn != null ? cn.flattenToString() : intent.getAction(),
+ sourceMetricsCategory);
+ }
+
+ /**
+ * Logs an event when the intent is started by Profile select dialog.
+ *
+ * @return true if the intent is loggable, otherwise false
+ */
+ public boolean logStartedIntentWithProfile(Intent intent, int sourceMetricsCategory,
+ boolean isWorkProfile) {
if (intent == null) {
- return;
+ return false;
}
final ComponentName cn = intent.getComponent();
- if (cn == null) {
- final String action = intent.getAction();
- if (TextUtils.isEmpty(action)) {
- // Not loggable
- return;
- }
- action(sourceMetricsCategory,
- MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
- SettingsEnums.PAGE_UNKNOWN,
- action,
- 0);
- return;
- } else if (TextUtils.equals(cn.getPackageName(), context.getPackageName())) {
- // Going to a Setting internal page, skip click logging in favor of page's own
- // visibility logging.
- return;
+ final String key = cn != null ? cn.flattenToString() : intent.getAction();
+ return logSettingsTileClick(key + (isWorkProfile ? "/work" : "/personal"),
+ sourceMetricsCategory);
+ }
+
+ /**
+ * Logs an event when the setting key is clicked.
+ *
+ * @return true if the key is loggable, otherwise false
+ */
+ public boolean logSettingsTileClick(String logKey, int sourceMetricsCategory) {
+ if (TextUtils.isEmpty(logKey)) {
+ // Not loggable
+ return false;
}
action(sourceMetricsCategory,
MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
SettingsEnums.PAGE_UNKNOWN,
- cn.flattenToString(),
+ logKey,
0);
+ return true;
}
-
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
index 8090169a4245..6e7a429e6b7a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
@@ -23,15 +23,16 @@ import android.content.Intent;
import android.os.SystemClock;
import androidx.lifecycle.Lifecycle.Event;
-import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import com.android.internal.logging.nano.MetricsProto;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnAttach;
/**
* Logs visibility change of a fragment.
*/
-public class VisibilityLoggerMixin implements LifecycleObserver {
+public class VisibilityLoggerMixin implements LifecycleObserver, OnAttach {
private static final String TAG = "VisibilityLoggerMixin";
@@ -39,6 +40,7 @@ public class VisibilityLoggerMixin implements LifecycleObserver {
private MetricsFeatureProvider mMetricsFeature;
private int mSourceMetricsCategory = MetricsProto.MetricsEvent.VIEW_UNKNOWN;
+ private long mCreationTimestamp;
private long mVisibleTimestamp;
public VisibilityLoggerMixin(int metricsCategory, MetricsFeatureProvider metricsFeature) {
@@ -46,19 +48,48 @@ public class VisibilityLoggerMixin implements LifecycleObserver {
mMetricsFeature = metricsFeature;
}
+ @Override
+ public void onAttach() {
+ mCreationTimestamp = SystemClock.elapsedRealtime();
+ }
+
@OnLifecycleEvent(Event.ON_RESUME)
public void onResume() {
+ if (mMetricsFeature == null || mMetricsCategory == METRICS_CATEGORY_UNKNOWN) {
+ return;
+ }
mVisibleTimestamp = SystemClock.elapsedRealtime();
- if (mMetricsFeature != null && mMetricsCategory != METRICS_CATEGORY_UNKNOWN) {
- mMetricsFeature.visible(null /* context */, mSourceMetricsCategory, mMetricsCategory);
+ if (mCreationTimestamp != 0L) {
+ final int elapse = (int) (mVisibleTimestamp - mCreationTimestamp);
+ mMetricsFeature.visible(null /* context */, mSourceMetricsCategory,
+ mMetricsCategory, elapse);
+ } else {
+ mMetricsFeature.visible(null /* context */, mSourceMetricsCategory,
+ mMetricsCategory, 0);
}
}
@OnLifecycleEvent(Event.ON_PAUSE)
public void onPause() {
- mVisibleTimestamp = 0;
+ mCreationTimestamp = 0;
if (mMetricsFeature != null && mMetricsCategory != METRICS_CATEGORY_UNKNOWN) {
- mMetricsFeature.hidden(null /* context */, mMetricsCategory);
+ final int elapse = (int) (SystemClock.elapsedRealtime() - mVisibleTimestamp);
+ mMetricsFeature.hidden(null /* context */, mMetricsCategory, elapse);
+ }
+ }
+
+ /**
+ * Logs the elapsed time from onAttach to calling {@link #writeElapsedTimeMetric(int, String)}.
+ * @param action : The value of the Action Enums.
+ * @param key : The value of special key string.
+ */
+ public void writeElapsedTimeMetric(int action, String key) {
+ if (mMetricsFeature == null || mMetricsCategory == METRICS_CATEGORY_UNKNOWN) {
+ return;
+ }
+ if (mCreationTimestamp != 0L) {
+ final int elapse = (int) (SystemClock.elapsedRealtime() - mCreationTimestamp);
+ mMetricsFeature.action(METRICS_CATEGORY_UNKNOWN, action, mMetricsCategory, key, elapse);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/Lifecycle.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/Lifecycle.java
index 56de280a0049..f87c88695686 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/Lifecycle.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/Lifecycle.java
@@ -94,11 +94,14 @@ public class Lifecycle extends LifecycleRegistry {
}
}
+ /**
+ * Pass all onAttach event to {@link LifecycleObserver}.
+ */
public void onAttach(Context context) {
for (int i = 0, size = mObservers.size(); i < size; i++) {
final LifecycleObserver observer = mObservers.get(i);
if (observer instanceof OnAttach) {
- ((OnAttach) observer).onAttach(context);
+ ((OnAttach) observer).onAttach();
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnAttach.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnAttach.java
index e28c38736639..1e7d01c15fbe 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnAttach.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnAttach.java
@@ -15,12 +15,12 @@
*/
package com.android.settingslib.core.lifecycle.events;
-import android.content.Context;
-
/**
- * @deprecated pass {@link Context} in constructor instead
+ * An Interface used by {@link LifecycleObserver} which changes to onAttach state.
*/
-@Deprecated
public interface OnAttach {
- void onAttach(Context context);
+ /**
+ * Called when {@link LifecycleObserver} is entering onAttach
+ */
+ void onAttach();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java
index a5f403690dab..d427f7a20dba 100644
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java
@@ -23,7 +23,9 @@ import android.net.wifi.WifiManager;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsMmTelManager;
+import android.telephony.ims.RegistrationManager;
+import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
@@ -32,19 +34,30 @@ import androidx.preference.PreferenceScreen;
import com.android.settingslib.R;
import com.android.settingslib.core.lifecycle.Lifecycle;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
/**
* Preference controller for IMS status
*/
public abstract class AbstractImsStatusPreferenceController
extends AbstractConnectivityPreferenceController {
+ private static final String LOG_TAG = "AbstractImsPrefController";
+
@VisibleForTesting
static final String KEY_IMS_REGISTRATION_STATE = "ims_reg_state";
+ private static final long MAX_THREAD_BLOCKING_TIME_MS = 2000;
+
private static final String[] CONNECTIVITY_INTENTS = {
BluetoothAdapter.ACTION_STATE_CHANGED,
ConnectivityManager.CONNECTIVITY_ACTION,
- WifiManager.LINK_CONFIGURATION_CHANGED_ACTION,
+ WifiManager.ACTION_LINK_CONFIGURATION_CHANGED,
WifiManager.NETWORK_STATE_CHANGED_ACTION,
};
@@ -57,8 +70,9 @@ public abstract class AbstractImsStatusPreferenceController
@Override
public boolean isAvailable() {
- CarrierConfigManager configManager = mContext.getSystemService(CarrierConfigManager.class);
- int subId = SubscriptionManager.getDefaultDataSubscriptionId();
+ final CarrierConfigManager configManager =
+ mContext.getSystemService(CarrierConfigManager.class);
+ final int subId = SubscriptionManager.getDefaultDataSubscriptionId();
PersistableBundle config = null;
if (configManager != null) {
config = configManager.getConfigForSubId(subId);
@@ -86,11 +100,57 @@ public abstract class AbstractImsStatusPreferenceController
@Override
protected void updateConnectivity() {
- int subId = SubscriptionManager.getDefaultDataSubscriptionId();
- if (mImsStatus != null) {
- TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
- mImsStatus.setSummary((tm != null && tm.isImsRegistered(subId)) ?
- R.string.ims_reg_status_registered : R.string.ims_reg_status_not_registered);
+ if (mImsStatus == null) {
+ return;
+ }
+ final int subId = SubscriptionManager.getDefaultDataSubscriptionId();
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ mImsStatus.setSummary(R.string.ims_reg_status_not_registered);
+ return;
+ }
+ final ExecutorService executors = Executors.newSingleThreadExecutor();
+ final StateCallback stateCallback = new StateCallback();
+
+ final ImsMmTelManager imsMmTelManager = ImsMmTelManager.createForSubscriptionId(subId);
+ try {
+ imsMmTelManager.getRegistrationState(executors, stateCallback);
+ } catch (Exception ex) {
+ }
+
+ mImsStatus.setSummary(stateCallback.waitUntilResult()
+ ? R.string.ims_reg_status_registered : R.string.ims_reg_status_not_registered);
+
+ try {
+ executors.shutdownNow();
+ } catch (Exception exception) {
+ }
+ }
+
+ private final class StateCallback extends AtomicBoolean implements Consumer<Integer> {
+ private StateCallback() {
+ super(false);
+ mSemaphore = new Semaphore(0);
+ }
+
+ private final Semaphore mSemaphore;
+
+ public void accept(Integer state) {
+ set(state == RegistrationManager.REGISTRATION_STATE_REGISTERED);
+ try {
+ mSemaphore.release();
+ } catch (Exception ex) {
+ }
+ }
+
+ public boolean waitUntilResult() {
+ try {
+ if (!mSemaphore.tryAcquire(MAX_THREAD_BLOCKING_TIME_MS, TimeUnit.MILLISECONDS)) {
+ Log.w(LOG_TAG, "IMS registration state query timeout");
+ return false;
+ }
+ } catch (Exception ex) {
+ }
+ return get();
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java
index 24da72ea611a..3bb3a0c412a5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java
@@ -42,7 +42,7 @@ public abstract class AbstractIpAddressPreferenceController
private static final String[] CONNECTIVITY_INTENTS = {
ConnectivityManager.CONNECTIVITY_ACTION,
- WifiManager.LINK_CONFIGURATION_CHANGED_ACTION,
+ WifiManager.ACTION_LINK_CONFIGURATION_CHANGED,
WifiManager.NETWORK_STATE_CHANGED_ACTION,
};
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
index 71778215e079..b5f275b463f4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
@@ -44,7 +44,7 @@ public abstract class AbstractWifiMacAddressPreferenceController
private static final String[] CONNECTIVITY_INTENTS = {
ConnectivityManager.CONNECTIVITY_ACTION,
- WifiManager.LINK_CONFIGURATION_CHANGED_ACTION,
+ WifiManager.ACTION_LINK_CONFIGURATION_CHANGED,
WifiManager.NETWORK_STATE_CHANGED_ACTION,
};
@@ -69,8 +69,10 @@ public abstract class AbstractWifiMacAddressPreferenceController
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
- mWifiMacAddress = screen.findPreference(KEY_WIFI_MAC_ADDRESS);
- updateConnectivity();
+ if (isAvailable()) {
+ mWifiMacAddress = screen.findPreference(KEY_WIFI_MAC_ADDRESS);
+ updateConnectivity();
+ }
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/display/BrightnessUtils.java b/packages/SettingsLib/src/com/android/settingslib/display/BrightnessUtils.java
index 55723f9d8ed4..4f86afaa995c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/display/BrightnessUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/display/BrightnessUtils.java
@@ -20,7 +20,8 @@ import android.util.MathUtils;
public class BrightnessUtils {
- public static final int GAMMA_SPACE_MAX = 1023;
+ public static final int GAMMA_SPACE_MIN = 0;
+ public static final int GAMMA_SPACE_MAX = 65535;
// Hybrid Log Gamma constant values
private static final float R = 0.5f;
@@ -51,7 +52,7 @@ public class BrightnessUtils {
* @return The corresponding setting value.
*/
public static final int convertGammaToLinear(int val, int min, int max) {
- final float normalizedVal = MathUtils.norm(0, GAMMA_SPACE_MAX, val);
+ final float normalizedVal = MathUtils.norm(GAMMA_SPACE_MIN, GAMMA_SPACE_MAX, val);
final float ret;
if (normalizedVal <= R) {
ret = MathUtils.sq(normalizedVal / R);
@@ -65,6 +66,33 @@ public class BrightnessUtils {
}
/**
+ * Version of {@link #convertGammaToLinear} that takes and returns float values.
+ * TODO(flc): refactor Android Auto to use float version
+ *
+ * @param val The slider value.
+ * @param min The minimum acceptable value for the setting.
+ * @param max The maximum acceptable value for the setting.
+ * @return The corresponding setting value.
+ */
+ public static final float convertGammaToLinearFloat(int val, float min, float max) {
+ final float normalizedVal = MathUtils.norm(GAMMA_SPACE_MIN, GAMMA_SPACE_MAX, val);
+ final float ret;
+ if (normalizedVal <= R) {
+ ret = MathUtils.sq(normalizedVal / R);
+ } else {
+ ret = MathUtils.exp((normalizedVal - C) / A) + B;
+ }
+
+ // HLG is normalized to the range [0, 12], ensure that value is within that range,
+ // it shouldn't be out of bounds.
+ final float normalizedRet = MathUtils.constrain(ret, 0, 12);
+
+ // Re-normalize to the range [0, 1]
+ // in order to derive the correct setting value.
+ return MathUtils.lerp(min, max, normalizedRet / 12);
+ }
+
+ /**
* A function for converting from the linear space that the setting works in to the
* gamma space that the slider works in.
*
@@ -87,6 +115,18 @@ public class BrightnessUtils {
* @return The corresponding slider value
*/
public static final int convertLinearToGamma(int val, int min, int max) {
+ return convertLinearToGammaFloat((float) val, (float) min, (float) max);
+ }
+
+ /**
+ * Version of {@link #convertLinearToGamma} that takes float values.
+ * TODO: brightnessfloat merge with above method(?)
+ * @param val The brightness setting value.
+ * @param min The minimum acceptable value for the setting.
+ * @param max The maximum acceptable value for the setting.
+ * @return The corresponding slider value
+ */
+ public static final int convertLinearToGammaFloat(float val, float min, float max) {
// For some reason, HLG normalizes to the range [0, 12] rather than [0, 1]
final float normalizedVal = MathUtils.norm(min, max, val) * 12;
final float ret;
@@ -96,6 +136,6 @@ public class BrightnessUtils {
ret = A * MathUtils.log(normalizedVal - B) + C;
}
- return Math.round(MathUtils.lerp(0, GAMMA_SPACE_MAX, ret));
+ return Math.round(MathUtils.lerp(GAMMA_SPACE_MIN, GAMMA_SPACE_MAX, ret));
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java b/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
index e0ca1ab0c07c..a38091debb64 100644
--- a/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
@@ -97,7 +97,7 @@ public class DisplayDensityUtils {
final Resources res = context.getResources();
final DisplayMetrics metrics = new DisplayMetrics();
- context.getDisplay().getRealMetrics(metrics);
+ context.getDisplayNoVerify().getRealMetrics(metrics);
final int currentDensity = metrics.densityDpi;
int currentDensityIndex = -1;
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawable/CircleFramedDrawable.java b/packages/SettingsLib/src/com/android/settingslib/drawable/CircleFramedDrawable.java
index 278b57da0c28..e5ea4467517b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawable/CircleFramedDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawable/CircleFramedDrawable.java
@@ -41,7 +41,7 @@ public class CircleFramedDrawable extends Drawable {
private final Bitmap mBitmap;
private final int mSize;
- private final Paint mPaint;
+ private Paint mIconPaint;
private float mScale;
private Rect mSrcRect;
@@ -75,18 +75,18 @@ public class CircleFramedDrawable extends Drawable {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
// opaque circle matte
- mPaint = new Paint();
- mPaint.setAntiAlias(true);
- mPaint.setColor(Color.BLACK);
- mPaint.setStyle(Paint.Style.FILL);
- canvas.drawPath(fillPath, mPaint);
+ Paint paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setColor(Color.BLACK);
+ paint.setStyle(Paint.Style.FILL);
+ canvas.drawPath(fillPath, paint);
// mask in the icon where the bitmap is opaque
- mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
- canvas.drawBitmap(icon, cropRect, circleRect, mPaint);
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
+ canvas.drawBitmap(icon, cropRect, circleRect, paint);
// prepare paint for frame drawing
- mPaint.setXfermode(null);
+ paint.setXfermode(null);
mScale = 1f;
@@ -100,7 +100,7 @@ public class CircleFramedDrawable extends Drawable {
final float pad = (mSize - inside) / 2f;
mDstRect.set(pad, pad, mSize - pad, mSize - pad);
- canvas.drawBitmap(mBitmap, mSrcRect, mDstRect, null);
+ canvas.drawBitmap(mBitmap, mSrcRect, mDstRect, mIconPaint);
}
public void setScale(float scale) {
@@ -122,8 +122,12 @@ public class CircleFramedDrawable extends Drawable {
@Override
public void setColorFilter(ColorFilter cf) {
+ if (mIconPaint == null) {
+ mIconPaint = new Paint();
+ }
+ mIconPaint.setColorFilter(cf);
}
-
+
@Override
public int getIntrinsicWidth() {
return mSize;
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
index 4ab9a9ac5915..b07fc2bee3f9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
@@ -61,6 +61,8 @@ public final class CategoryKey {
"com.android.settings.category.ia.my_device_info";
public static final String CATEGORY_BATTERY_SAVER_SETTINGS =
"com.android.settings.category.ia.battery_saver_settings";
+ public static final String CATEGORY_SMART_BATTERY_SETTINGS =
+ "com.android.settings.category.ia.smart_battery_settings";
public static final Map<String, String> KEY_COMPAT_MAP;
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index 3c0f6fe8ccbb..ab7b54d98285 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -136,7 +136,7 @@ public class DreamBackend {
if (mDreamManager == null)
return null;
try {
- return mDreamManager.getDefaultDreamComponent();
+ return mDreamManager.getDefaultDreamComponentForUser(mContext.getUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed to get default dream", e);
return null;
@@ -159,6 +159,25 @@ public class DreamBackend {
return null;
}
+ /**
+ * Gets an icon from active dream.
+ */
+ public Drawable getActiveIcon() {
+ final ComponentName cn = getActiveDream();
+ if (cn != null) {
+ final PackageManager pm = mContext.getPackageManager();
+ try {
+ final ServiceInfo ri = pm.getServiceInfo(cn, 0);
+ if (ri != null) {
+ return ri.loadIcon(pm);
+ }
+ } catch (PackageManager.NameNotFoundException exc) {
+ return null;
+ }
+ }
+ return null;
+ }
+
public @WhenToDream int getWhenToDreamSetting() {
if (!isEnabled()) {
return NEVER;
@@ -269,7 +288,7 @@ public class DreamBackend {
if (mDreamManager == null || dreamInfo == null || dreamInfo.componentName == null)
return;
try {
- mDreamManager.testDream(dreamInfo.componentName);
+ mDreamManager.testDream(mContext.getUserId(), dreamInfo.componentName);
} catch (RemoteException e) {
Log.w(TAG, "Failed to preview " + dreamInfo, e);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
index e19ac815b939..6d7e86f64944 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
@@ -21,6 +21,7 @@ import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.PowerManager;
+import android.os.UserHandle;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
import android.text.TextUtils;
@@ -186,7 +187,8 @@ public class BatterySaverUtils {
}
private static void setBatterySaverConfirmationAcknowledged(Context context) {
- Secure.putInt(context.getContentResolver(), Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 1);
+ Secure.putIntForUser(context.getContentResolver(), Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 1,
+ UserHandle.USER_CURRENT);
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
new file mode 100644
index 000000000000..bc40903d88e4
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -0,0 +1,147 @@
+/*
+ * 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.settingslib.fuelgauge;
+
+import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
+import static android.os.BatteryManager.BATTERY_STATUS_FULL;
+import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
+import static android.os.BatteryManager.EXTRA_HEALTH;
+import static android.os.BatteryManager.EXTRA_LEVEL;
+import static android.os.BatteryManager.EXTRA_MAX_CHARGING_CURRENT;
+import static android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE;
+import static android.os.BatteryManager.EXTRA_PLUGGED;
+import static android.os.BatteryManager.EXTRA_STATUS;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.BatteryManager;
+
+import com.android.settingslib.R;
+
+/**
+ * Stores and computes some battery information.
+ */
+public class BatteryStatus {
+ private static final int LOW_BATTERY_THRESHOLD = 20;
+ private static final int DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT = 5000000;
+
+ public static final int CHARGING_UNKNOWN = -1;
+ public static final int CHARGING_SLOWLY = 0;
+ public static final int CHARGING_REGULAR = 1;
+ public static final int CHARGING_FAST = 2;
+
+ public final int status;
+ public final int level;
+ public final int plugged;
+ public final int health;
+ public final int maxChargingWattage;
+
+ public BatteryStatus(int status, int level, int plugged, int health,
+ int maxChargingWattage) {
+ this.status = status;
+ this.level = level;
+ this.plugged = plugged;
+ this.health = health;
+ this.maxChargingWattage = maxChargingWattage;
+ }
+
+ public BatteryStatus(Intent batteryChangedIntent) {
+ status = batteryChangedIntent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN);
+ plugged = batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, 0);
+ level = batteryChangedIntent.getIntExtra(EXTRA_LEVEL, 0);
+ health = batteryChangedIntent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);
+
+ final int maxChargingMicroAmp = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT,
+ -1);
+ int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1);
+
+ if (maxChargingMicroVolt <= 0) {
+ maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT;
+ }
+ if (maxChargingMicroAmp > 0) {
+ // Calculating muW = muA * muV / (10^6 mu^2 / mu); splitting up the divisor
+ // to maintain precision equally on both factors.
+ maxChargingWattage = (maxChargingMicroAmp / 1000)
+ * (maxChargingMicroVolt / 1000);
+ } else {
+ maxChargingWattage = -1;
+ }
+ }
+
+ /**
+ * Determine whether the device is plugged in (USB, power, or wireless).
+ *
+ * @return true if the device is plugged in.
+ */
+ public boolean isPluggedIn() {
+ return plugged == BatteryManager.BATTERY_PLUGGED_AC
+ || plugged == BatteryManager.BATTERY_PLUGGED_USB
+ || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS;
+ }
+
+ /**
+ * Determine whether the device is plugged in (USB, power).
+ *
+ * @return true if the device is plugged in wired (as opposed to wireless)
+ */
+ public boolean isPluggedInWired() {
+ return plugged == BatteryManager.BATTERY_PLUGGED_AC
+ || plugged == BatteryManager.BATTERY_PLUGGED_USB;
+ }
+
+ /**
+ * Whether or not the device is charged. Note that some devices never return 100% for
+ * battery level, so this allows either battery level or status to determine if the
+ * battery is charged.
+ *
+ * @return true if the device is charged
+ */
+ public boolean isCharged() {
+ return status == BATTERY_STATUS_FULL || level >= 100;
+ }
+
+ /**
+ * Whether battery is low and needs to be charged.
+ *
+ * @return true if battery is low
+ */
+ public boolean isBatteryLow() {
+ return level < LOW_BATTERY_THRESHOLD;
+ }
+
+ /**
+ * Return current chargin speed is fast, slow or normal.
+ *
+ * @return the charing speed
+ */
+ public final int getChargingSpeed(Context context) {
+ final int slowThreshold = context.getResources().getInteger(
+ R.integer.config_chargingSlowlyThreshold);
+ final int fastThreshold = context.getResources().getInteger(
+ R.integer.config_chargingFastThreshold);
+ return maxChargingWattage <= 0 ? CHARGING_UNKNOWN :
+ maxChargingWattage < slowThreshold ? CHARGING_SLOWLY :
+ maxChargingWattage > fastThreshold ? CHARGING_FAST :
+ CHARGING_REGULAR;
+ }
+
+ @Override
+ public String toString() {
+ return "BatteryStatus{status=" + status + ",level=" + level + ",plugged=" + plugged
+ + ",health=" + health + ",maxChargingWattage=" + maxChargingWattage + "}";
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
index 55b6cda5548c..546095e9014a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
@@ -98,6 +98,7 @@ public class InputMethodPreference extends RestrictedSwitchPreference implements
// Remove switch widget.
setWidgetLayoutResource(NO_WIDGET);
}
+ setIconSize(context.getResources().getDimensionPixelSize(R.dimen.secondary_app_icon_size));
}
@VisibleForTesting
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
index 104cc8f9841c..d3315efa0656 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
@@ -235,7 +235,7 @@ public class RecentLocationApps {
public final CharSequence contentDescription;
public final long requestFinishTime;
- private Request(String packageName, UserHandle userHandle, Drawable icon,
+ public Request(String packageName, UserHandle userHandle, Drawable icon,
CharSequence label, boolean isHighBattery, CharSequence contentDescription,
long requestFinishTime) {
this.packageName = packageName;
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
index ff40d8e00603..450bdb161933 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
@@ -202,6 +202,12 @@ public class SettingsInjector {
}
/**
+ * Gives descendants a chance to log Preference click event
+ */
+ protected void logPreferenceClick(Intent intent) {
+ }
+
+ /**
* Returns the settings parsed from the attributes of the
* {@link SettingInjectorService#META_DATA_NAME} tag, or null.
*
@@ -315,6 +321,7 @@ public class SettingsInjector {
// Settings > Location.
Intent settingIntent = new Intent();
settingIntent.setClassName(mInfo.packageName, mInfo.settingsActivity);
+ logPreferenceClick(settingIntent);
// Sometimes the user may navigate back to "Settings" and launch another different
// injected setting after one injected setting has been launched.
//
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
index 3a53d29f7618..00f94f5c2e64 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
@@ -19,8 +19,8 @@ import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.graphics.drawable.Drawable;
-import android.util.Log;
-import android.util.Pair;
+import android.media.MediaRoute2Info;
+import android.media.MediaRouter2Manager;
import com.android.settingslib.R;
import com.android.settingslib.bluetooth.BluetoothUtils;
@@ -35,8 +35,9 @@ public class BluetoothMediaDevice extends MediaDevice {
private CachedBluetoothDevice mCachedDevice;
- BluetoothMediaDevice(Context context, CachedBluetoothDevice device) {
- super(context, MediaDeviceType.TYPE_BLUETOOTH_DEVICE);
+ BluetoothMediaDevice(Context context, CachedBluetoothDevice device,
+ MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName) {
+ super(context, routerManager, info, packageName);
mCachedDevice = device;
initDeviceRecord();
}
@@ -55,28 +56,23 @@ public class BluetoothMediaDevice extends MediaDevice {
@Override
public Drawable getIcon() {
- final Pair<Drawable, String> pair = BluetoothUtils
- .getBtRainbowDrawableWithDescription(mContext, mCachedDevice);
- return pair.first;
- }
-
- @Override
- public String getId() {
- return MediaDeviceUtils.getId(mCachedDevice);
+ final Drawable drawable = getIconWithoutBackground();
+ if (!isFastPairDevice()) {
+ setColorFilter(drawable);
+ }
+ return BluetoothUtils.buildAdvancedDrawable(mContext, drawable);
}
@Override
- public boolean connect() {
- //TODO(b/117129183): add callback to notify LocalMediaManager connection state.
- final boolean isConnected = mCachedDevice.setActive();
- setConnectedRecord();
- Log.d(TAG, "connect() device : " + getName() + ", is selected : " + isConnected);
- return isConnected;
+ public Drawable getIconWithoutBackground() {
+ return isFastPairDevice()
+ ? BluetoothUtils.getBtDrawableWithDescription(mContext, mCachedDevice).first
+ : mContext.getDrawable(R.drawable.ic_headphone);
}
@Override
- public void disconnect() {
- //TODO(b/117129183): disconnected last select device
+ public String getId() {
+ return MediaDeviceUtils.getId(mCachedDevice);
}
/**
@@ -101,6 +97,13 @@ public class BluetoothMediaDevice extends MediaDevice {
}
@Override
+ public boolean isFastPairDevice() {
+ return mCachedDevice != null
+ && BluetoothUtils.getBooleanMetaData(
+ mCachedDevice.getDevice(), BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET);
+ }
+
+ @Override
public boolean isConnected() {
return mCachedDevice.getBondState() == BluetoothDevice.BOND_BONDED
&& mCachedDevice.isConnected();
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java
deleted file mode 100644
index eb35c44bd690..000000000000
--- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java
+++ /dev/null
@@ -1,347 +0,0 @@
-/*
- * Copyright 2018 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.settingslib.media;
-
-import android.app.Notification;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
-import android.content.Context;
-import android.util.Log;
-
-import com.android.settingslib.bluetooth.A2dpProfile;
-import com.android.settingslib.bluetooth.BluetoothCallback;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
-import com.android.settingslib.bluetooth.HearingAidProfile;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * BluetoothMediaManager provide interface to get Bluetooth device list.
- */
-public class BluetoothMediaManager extends MediaManager implements BluetoothCallback,
- LocalBluetoothProfileManager.ServiceListener {
-
- private static final String TAG = "BluetoothMediaManager";
-
- private final DeviceAttributeChangeCallback mDeviceAttributeChangeCallback =
- new DeviceAttributeChangeCallback();
-
- private LocalBluetoothManager mLocalBluetoothManager;
- private LocalBluetoothProfileManager mProfileManager;
- private CachedBluetoothDeviceManager mCachedBluetoothDeviceManager;
-
- private MediaDevice mLastAddedDevice;
- private MediaDevice mLastRemovedDevice;
-
- private boolean mIsA2dpProfileReady = false;
- private boolean mIsHearingAidProfileReady = false;
-
- BluetoothMediaManager(Context context, LocalBluetoothManager localBluetoothManager,
- Notification notification) {
- super(context, notification);
-
- mLocalBluetoothManager = localBluetoothManager;
- mProfileManager = mLocalBluetoothManager.getProfileManager();
- mCachedBluetoothDeviceManager = mLocalBluetoothManager.getCachedDeviceManager();
- }
-
- @Override
- public void startScan() {
- mLocalBluetoothManager.getEventManager().registerCallback(this);
- buildBluetoothDeviceList();
- dispatchDeviceListAdded();
- addServiceListenerIfNecessary();
- }
-
- private void addServiceListenerIfNecessary() {
- // The profile may not ready when calling startScan().
- // Device status are all disconnected since profiles are not ready to connected.
- // In this case, we observe onServiceConnected() in LocalBluetoothProfileManager.
- // When A2dpProfile or HearingAidProfile is connected will call buildBluetoothDeviceList()
- // again to find the connected devices.
- if (!mIsA2dpProfileReady || !mIsHearingAidProfileReady) {
- mProfileManager.addServiceListener(this);
- }
- }
-
- private void buildBluetoothDeviceList() {
- mMediaDevices.clear();
- addConnectableA2dpDevices();
- addConnectableHearingAidDevices();
- }
-
- private void addConnectableA2dpDevices() {
- final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile();
- if (a2dpProfile == null) {
- Log.w(TAG, "addConnectableA2dpDevices() a2dp profile is null!");
- return;
- }
-
- final List<BluetoothDevice> devices = a2dpProfile.getConnectableDevices();
-
- for (BluetoothDevice device : devices) {
- final CachedBluetoothDevice cachedDevice =
- mCachedBluetoothDeviceManager.findDevice(device);
-
- if (cachedDevice == null) {
- Log.w(TAG, "Can't found CachedBluetoothDevice : " + device.getName());
- continue;
- }
-
- Log.d(TAG, "addConnectableA2dpDevices() device : " + cachedDevice.getName()
- + ", is connected : " + cachedDevice.isConnected()
- + ", is enabled : " + a2dpProfile.isEnabled(device));
-
- if (a2dpProfile.isEnabled(device)
- && BluetoothDevice.BOND_BONDED == cachedDevice.getBondState()) {
- addMediaDevice(cachedDevice);
- }
- }
-
- mIsA2dpProfileReady = a2dpProfile.isProfileReady();
- }
-
- private void addConnectableHearingAidDevices() {
- final HearingAidProfile hapProfile = mProfileManager.getHearingAidProfile();
- if (hapProfile == null) {
- Log.w(TAG, "addConnectableHearingAidDevices() hap profile is null!");
- return;
- }
-
- final List<Long> devicesHiSyncIds = new ArrayList<>();
- final List<BluetoothDevice> devices = hapProfile.getConnectableDevices();
-
- for (BluetoothDevice device : devices) {
- final CachedBluetoothDevice cachedDevice =
- mCachedBluetoothDeviceManager.findDevice(device);
-
- if (cachedDevice == null) {
- Log.w(TAG, "Can't found CachedBluetoothDevice : " + device.getName());
- continue;
- }
-
- Log.d(TAG, "addConnectableHearingAidDevices() device : " + cachedDevice.getName()
- + ", is connected : " + cachedDevice.isConnected()
- + ", is enabled : " + hapProfile.isEnabled(device));
-
- final long hiSyncId = hapProfile.getHiSyncId(device);
-
- // device with same hiSyncId should not be shown in the UI.
- // So do not add it into connectedDevices.
- if (!devicesHiSyncIds.contains(hiSyncId) && hapProfile.isEnabled(device)
- && BluetoothDevice.BOND_BONDED == cachedDevice.getBondState()) {
- devicesHiSyncIds.add(hiSyncId);
- addMediaDevice(cachedDevice);
- }
- }
-
- mIsHearingAidProfileReady = hapProfile.isProfileReady();
- }
-
- private void addMediaDevice(CachedBluetoothDevice cachedDevice) {
- MediaDevice mediaDevice = findMediaDevice(MediaDeviceUtils.getId(cachedDevice));
- if (mediaDevice == null) {
- mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice);
- cachedDevice.registerCallback(mDeviceAttributeChangeCallback);
- mLastAddedDevice = mediaDevice;
- mMediaDevices.add(mediaDevice);
- }
- }
-
- @Override
- public void stopScan() {
- mLocalBluetoothManager.getEventManager().unregisterCallback(this);
- unregisterDeviceAttributeChangeCallback();
- }
-
- private void unregisterDeviceAttributeChangeCallback() {
- for (MediaDevice device : mMediaDevices) {
- ((BluetoothMediaDevice) device).getCachedDevice()
- .unregisterCallback(mDeviceAttributeChangeCallback);
- }
- }
-
- @Override
- public void onBluetoothStateChanged(int bluetoothState) {
- if (BluetoothAdapter.STATE_ON == bluetoothState) {
- buildBluetoothDeviceList();
- dispatchDeviceListAdded();
- addServiceListenerIfNecessary();
- } else if (BluetoothAdapter.STATE_OFF == bluetoothState) {
- final List<MediaDevice> removeDevicesList = new ArrayList<>();
- for (MediaDevice device : mMediaDevices) {
- ((BluetoothMediaDevice) device).getCachedDevice()
- .unregisterCallback(mDeviceAttributeChangeCallback);
- removeDevicesList.add(device);
- }
- mMediaDevices.removeAll(removeDevicesList);
- dispatchDeviceListRemoved(removeDevicesList);
- }
- }
-
- @Override
- public void onAudioModeChanged() {
- dispatchDataChanged();
- }
-
- @Override
- public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
- if (isCachedDeviceConnected(cachedDevice)) {
- addMediaDevice(cachedDevice);
- dispatchDeviceAdded(cachedDevice);
- }
- }
-
- private boolean isCachedDeviceConnected(CachedBluetoothDevice cachedDevice) {
- final boolean isConnectedHearingAidDevice = cachedDevice.isConnectedHearingAidDevice();
- final boolean isConnectedA2dpDevice = cachedDevice.isConnectedA2dpDevice();
- Log.d(TAG, "isCachedDeviceConnected() cachedDevice : " + cachedDevice
- + ", is hearing aid connected : " + isConnectedHearingAidDevice
- + ", is a2dp connected : " + isConnectedA2dpDevice);
-
- return isConnectedHearingAidDevice || isConnectedA2dpDevice;
- }
-
- private void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) {
- if (mLastAddedDevice != null
- && MediaDeviceUtils.getId(cachedDevice) == mLastAddedDevice.getId()) {
- dispatchDeviceAdded(mLastAddedDevice);
- }
- }
-
- @Override
- public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
- if (!isCachedDeviceConnected(cachedDevice)) {
- removeMediaDevice(cachedDevice);
- dispatchDeviceRemoved(cachedDevice);
- }
- }
-
- private void removeMediaDevice(CachedBluetoothDevice cachedDevice) {
- final MediaDevice mediaDevice = findMediaDevice(MediaDeviceUtils.getId(cachedDevice));
- if (mediaDevice != null) {
- cachedDevice.unregisterCallback(mDeviceAttributeChangeCallback);
- mLastRemovedDevice = mediaDevice;
- mMediaDevices.remove(mediaDevice);
- }
- }
-
- void dispatchDeviceRemoved(CachedBluetoothDevice cachedDevice) {
- if (mLastRemovedDevice != null
- && MediaDeviceUtils.getId(cachedDevice) == mLastRemovedDevice.getId()) {
- dispatchDeviceRemoved(mLastRemovedDevice);
- }
- }
-
- @Override
- public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state,
- int bluetoothProfile) {
- Log.d(TAG, "onProfileConnectionStateChanged() device: " + cachedDevice
- + ", state: " + state + ", bluetoothProfile: " + bluetoothProfile);
-
- updateMediaDeviceListIfNecessary(cachedDevice);
- }
-
- private void updateMediaDeviceListIfNecessary(CachedBluetoothDevice cachedDevice) {
- if (BluetoothDevice.BOND_NONE == cachedDevice.getBondState()) {
- removeMediaDevice(cachedDevice);
- dispatchDeviceRemoved(cachedDevice);
- } else {
- if (findMediaDevice(MediaDeviceUtils.getId(cachedDevice)) != null) {
- dispatchDataChanged();
- }
- }
- }
-
- @Override
- public void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
- Log.d(TAG, "onAclConnectionStateChanged() device: " + cachedDevice + ", state: " + state);
-
- updateMediaDeviceListIfNecessary(cachedDevice);
- }
-
- @Override
- public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
- Log.d(TAG, "onActiveDeviceChanged : device : "
- + activeDevice + ", profile : " + bluetoothProfile);
-
- if (BluetoothProfile.HEARING_AID == bluetoothProfile) {
- if (activeDevice != null) {
- dispatchConnectedDeviceChanged(MediaDeviceUtils.getId(activeDevice));
- }
- } else if (BluetoothProfile.A2DP == bluetoothProfile) {
- // When active device change to Hearing Aid,
- // BluetoothEventManager also send onActiveDeviceChanged() to notify that active device
- // of A2DP profile is null. To handle this case, check hearing aid device
- // is active device or not
- final MediaDevice activeHearingAidDevice = findActiveHearingAidDevice();
- final String id = activeDevice == null
- ? activeHearingAidDevice == null
- ? PhoneMediaDevice.ID : activeHearingAidDevice.getId()
- : MediaDeviceUtils.getId(activeDevice);
- dispatchConnectedDeviceChanged(id);
- }
- }
-
- private MediaDevice findActiveHearingAidDevice() {
- final HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile();
-
- if (hearingAidProfile != null) {
- final List<BluetoothDevice> activeDevices = hearingAidProfile.getActiveDevices();
- for (BluetoothDevice btDevice : activeDevices) {
- if (btDevice != null) {
- return findMediaDevice(MediaDeviceUtils.getId(btDevice));
- }
- }
- }
- return null;
- }
-
- @Override
- public void onServiceConnected() {
- if (!mIsA2dpProfileReady || !mIsHearingAidProfileReady) {
- buildBluetoothDeviceList();
- dispatchDeviceListAdded();
- }
-
- //Remove the listener once a2dpProfile and hearingAidProfile are ready.
- if (mIsA2dpProfileReady && mIsHearingAidProfileReady) {
- mProfileManager.removeServiceListener(this);
- }
- }
-
- @Override
- public void onServiceDisconnected() {
-
- }
-
- /**
- * This callback is for update {@link BluetoothMediaDevice} summary when
- * {@link CachedBluetoothDevice} connection state is changed.
- */
- private class DeviceAttributeChangeCallback implements CachedBluetoothDevice.Callback {
-
- @Override
- public void onDeviceAttributesChanged() {
- dispatchDataChanged();
- }
- }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
index 732e8dba3e44..949b2456042c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
@@ -15,15 +15,24 @@
*/
package com.android.settingslib.media;
+import static android.media.MediaRoute2Info.FEATURE_REMOTE_GROUP_PLAYBACK;
+import static android.media.MediaRoute2Info.FEATURE_REMOTE_VIDEO_PLAYBACK;
+import static android.media.MediaRoute2Info.TYPE_GROUP;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
+
import android.content.Context;
import android.graphics.drawable.Drawable;
-import android.widget.Toast;
+import android.media.MediaRoute2Info;
+import android.media.MediaRouter2Manager;
-import androidx.mediarouter.media.MediaRouter;
+import androidx.annotation.VisibleForTesting;
import com.android.settingslib.R;
import com.android.settingslib.bluetooth.BluetoothUtils;
+import java.util.List;
+
/**
* InfoMediaDevice extends MediaDevice to represents wifi device.
*/
@@ -31,50 +40,73 @@ public class InfoMediaDevice extends MediaDevice {
private static final String TAG = "InfoMediaDevice";
- private MediaRouter.RouteInfo mRouteInfo;
-
- InfoMediaDevice(Context context, MediaRouter.RouteInfo info) {
- super(context, MediaDeviceType.TYPE_CAST_DEVICE);
- mRouteInfo = info;
+ InfoMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
+ String packageName) {
+ super(context, routerManager, info, packageName);
initDeviceRecord();
}
@Override
public String getName() {
- return mRouteInfo.getName();
+ return mRouteInfo.getName().toString();
}
@Override
public String getSummary() {
- return null;
+ return mRouteInfo.getClientPackageName() != null
+ ? mContext.getString(R.string.bluetooth_active_no_battery_level) : null;
}
@Override
public Drawable getIcon() {
- //TODO(b/120669861): Return remote device icon uri once api is ready.
- return BluetoothUtils.buildBtRainbowDrawable(mContext,
- mContext.getDrawable(R.drawable.ic_media_device), getId().hashCode());
+ final Drawable drawable = getIconWithoutBackground();
+ setColorFilter(drawable);
+ return BluetoothUtils.buildAdvancedDrawable(mContext, drawable);
}
@Override
- public String getId() {
- return MediaDeviceUtils.getId(mRouteInfo);
+ public Drawable getIconWithoutBackground() {
+ return mContext.getDrawable(getDrawableResIdByFeature());
}
- @Override
- public boolean connect() {
- //TODO(b/121083246): use SystemApi to transfer media
- setConnectedRecord();
- Toast.makeText(mContext, "This is cast device !", Toast.LENGTH_SHORT).show();
- return false;
+ @VisibleForTesting
+ int getDrawableResId() {
+ int resId;
+ switch (mRouteInfo.getType()) {
+ case TYPE_GROUP:
+ resId = R.drawable.ic_media_group_device;
+ break;
+ case TYPE_REMOTE_TV:
+ resId = R.drawable.ic_media_display_device;
+ break;
+ case TYPE_REMOTE_SPEAKER:
+ default:
+ resId = R.drawable.ic_media_speaker_device;
+ break;
+ }
+ return resId;
}
- @Override
- public void disconnect() {
- //TODO(b/121083246): disconnected last select device
+ @VisibleForTesting
+ int getDrawableResIdByFeature() {
+ int resId;
+ final List<String> features = mRouteInfo.getFeatures();
+ if (features.contains(FEATURE_REMOTE_GROUP_PLAYBACK)) {
+ resId = R.drawable.ic_media_group_device;
+ } else if (features.contains(FEATURE_REMOTE_VIDEO_PLAYBACK)) {
+ resId = R.drawable.ic_media_display_device;
+ } else {
+ resId = R.drawable.ic_media_speaker_device;
+ }
+
+ return resId;
}
@Override
+ public String getId() {
+ return MediaDeviceUtils.getId(mRouteInfo);
+ }
+
public boolean isConnected() {
return true;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index bc8e2c35291d..6c7e03f104dd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -15,14 +15,40 @@
*/
package com.android.settingslib.media;
+import static android.media.MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_DOCK;
+import static android.media.MediaRoute2Info.TYPE_GROUP;
+import static android.media.MediaRoute2Info.TYPE_HDMI;
+import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
+import static android.media.MediaRoute2Info.TYPE_UNKNOWN;
+import static android.media.MediaRoute2Info.TYPE_USB_ACCESSORY;
+import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
+import static android.media.MediaRoute2Info.TYPE_USB_HEADSET;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
+import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
+
import android.app.Notification;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
import android.content.Context;
+import android.media.MediaRoute2Info;
+import android.media.MediaRouter2Manager;
+import android.media.RoutingSessionInfo;
+import android.text.TextUtils;
import android.util.Log;
-import androidx.mediarouter.media.MediaRouteSelector;
-import androidx.mediarouter.media.MediaRouter;
-
import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
/**
* InfoMediaManager provide interface to get InfoMediaDevice list.
@@ -30,65 +56,456 @@ import com.android.internal.annotations.VisibleForTesting;
public class InfoMediaManager extends MediaManager {
private static final String TAG = "InfoMediaManager";
-
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ @VisibleForTesting
+ final RouterManagerCallback mMediaRouterCallback = new RouterManagerCallback();
@VisibleForTesting
- final MediaRouterCallback mMediaRouterCallback = new MediaRouterCallback();
+ final Executor mExecutor = Executors.newSingleThreadExecutor();
@VisibleForTesting
- MediaRouteSelector mSelector;
+ MediaRouter2Manager mRouterManager;
@VisibleForTesting
- MediaRouter mMediaRouter;
+ String mPackageName;
- private String mPackageName;
+ private MediaDevice mCurrentConnectedDevice;
+ private LocalBluetoothManager mBluetoothManager;
- InfoMediaManager(Context context, String packageName, Notification notification) {
+ public InfoMediaManager(Context context, String packageName, Notification notification,
+ LocalBluetoothManager localBluetoothManager) {
super(context, notification);
- mMediaRouter = MediaRouter.getInstance(context);
- mPackageName = packageName;
- mSelector = new MediaRouteSelector.Builder()
- .addControlCategory(getControlCategoryByPackageName(mPackageName))
- .build();
+ mRouterManager = MediaRouter2Manager.getInstance(context);
+ mBluetoothManager = localBluetoothManager;
+ if (!TextUtils.isEmpty(packageName)) {
+ mPackageName = packageName;
+ }
}
@Override
public void startScan() {
mMediaDevices.clear();
- mMediaRouter.addCallback(mSelector, mMediaRouterCallback,
- MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
- }
-
- @VisibleForTesting
- String getControlCategoryByPackageName(String packageName) {
- //TODO(b/117129183): Use package name to get ControlCategory.
- //Since api not ready, return fixed ControlCategory for prototype.
- return "com.google.android.gms.cast.CATEGORY_CAST/4F8B3483";
+ mRouterManager.registerCallback(mExecutor, mMediaRouterCallback);
+ refreshDevices();
}
@Override
public void stopScan() {
- mMediaRouter.removeCallback(mMediaRouterCallback);
+ mRouterManager.unregisterCallback(mMediaRouterCallback);
+ }
+
+ /**
+ * Get current device that played media.
+ * @return MediaDevice
+ */
+ MediaDevice getCurrentConnectedDevice() {
+ return mCurrentConnectedDevice;
+ }
+
+ /**
+ * Transfer MediaDevice for media without package name.
+ */
+ boolean connectDeviceWithoutPackageName(MediaDevice device) {
+ boolean isConnected = false;
+ final List<RoutingSessionInfo> infos = mRouterManager.getActiveSessions();
+ if (infos.size() > 0) {
+ final RoutingSessionInfo info = infos.get(0);
+ mRouterManager.transfer(info, device.mRouteInfo);
+
+ isConnected = true;
+ }
+ return isConnected;
+ }
+
+ /**
+ * Add a MediaDevice to let it play current media.
+ *
+ * @param device MediaDevice
+ * @return If add device successful return {@code true}, otherwise return {@code false}
+ */
+ boolean addDeviceToPlayMedia(MediaDevice device) {
+ if (TextUtils.isEmpty(mPackageName)) {
+ Log.w(TAG, "addDeviceToPlayMedia() package name is null or empty!");
+ return false;
+ }
+
+ final RoutingSessionInfo info = getRoutingSessionInfo();
+ if (info != null && info.getSelectableRoutes().contains(device.mRouteInfo.getId())) {
+ mRouterManager.selectRoute(info, device.mRouteInfo);
+ return true;
+ }
+
+ Log.w(TAG, "addDeviceToPlayMedia() Ignoring selecting a non-selectable device : "
+ + device.getName());
+
+ return false;
+ }
+
+ private RoutingSessionInfo getRoutingSessionInfo() {
+ final List<RoutingSessionInfo> sessionInfos =
+ mRouterManager.getRoutingSessions(mPackageName);
+
+ return sessionInfos.get(sessionInfos.size() - 1);
+ }
+
+ /**
+ * Remove a {@code device} from current media.
+ *
+ * @param device MediaDevice
+ * @return If device stop successful return {@code true}, otherwise return {@code false}
+ */
+ boolean removeDeviceFromPlayMedia(MediaDevice device) {
+ if (TextUtils.isEmpty(mPackageName)) {
+ Log.w(TAG, "removeDeviceFromMedia() package name is null or empty!");
+ return false;
+ }
+
+ final RoutingSessionInfo info = getRoutingSessionInfo();
+ if (info != null && info.getSelectedRoutes().contains(device.mRouteInfo.getId())) {
+ mRouterManager.deselectRoute(info, device.mRouteInfo);
+ return true;
+ }
+
+ Log.w(TAG, "removeDeviceFromMedia() Ignoring deselecting a non-deselectable device : "
+ + device.getName());
+
+ return false;
+ }
+
+ /**
+ * Release session to stop playing media on MediaDevice.
+ */
+ boolean releaseSession() {
+ if (TextUtils.isEmpty(mPackageName)) {
+ Log.w(TAG, "releaseSession() package name is null or empty!");
+ return false;
+ }
+
+ final RoutingSessionInfo sessionInfo = getRoutingSessionInfo();
+
+ if (sessionInfo != null) {
+ mRouterManager.releaseSession(sessionInfo);
+ return true;
+ }
+
+ Log.w(TAG, "releaseSession() Ignoring release session : " + mPackageName);
+
+ return false;
+ }
+
+ /**
+ * Get the MediaDevice list that can be added to current media.
+ *
+ * @return list of MediaDevice
+ */
+ List<MediaDevice> getSelectableMediaDevice() {
+ final List<MediaDevice> deviceList = new ArrayList<>();
+ if (TextUtils.isEmpty(mPackageName)) {
+ Log.w(TAG, "getSelectableMediaDevice() package name is null or empty!");
+ return deviceList;
+ }
+
+ final RoutingSessionInfo info = getRoutingSessionInfo();
+ if (info != null) {
+ for (MediaRoute2Info route : mRouterManager.getSelectableRoutes(info)) {
+ deviceList.add(new InfoMediaDevice(mContext, mRouterManager,
+ route, mPackageName));
+ }
+ return deviceList;
+ }
+
+ Log.w(TAG, "getSelectableMediaDevice() cannot found selectable MediaDevice from : "
+ + mPackageName);
+
+ return deviceList;
+ }
+
+ /**
+ * Get the MediaDevice list that can be removed from current media session.
+ *
+ * @return list of MediaDevice
+ */
+ List<MediaDevice> getDeselectableMediaDevice() {
+ final List<MediaDevice> deviceList = new ArrayList<>();
+ if (TextUtils.isEmpty(mPackageName)) {
+ Log.d(TAG, "getDeselectableMediaDevice() package name is null or empty!");
+ return deviceList;
+ }
+
+ final RoutingSessionInfo info = getRoutingSessionInfo();
+ if (info != null) {
+ for (MediaRoute2Info route : mRouterManager.getDeselectableRoutes(info)) {
+ deviceList.add(new InfoMediaDevice(mContext, mRouterManager,
+ route, mPackageName));
+ Log.d(TAG, route.getName() + " is deselectable for " + mPackageName);
+ }
+ return deviceList;
+ }
+ Log.d(TAG, "getDeselectableMediaDevice() cannot found deselectable MediaDevice from : "
+ + mPackageName);
+
+ return deviceList;
+ }
+
+ /**
+ * Get the MediaDevice list that has been selected to current media.
+ *
+ * @return list of MediaDevice
+ */
+ List<MediaDevice> getSelectedMediaDevice() {
+ final List<MediaDevice> deviceList = new ArrayList<>();
+ if (TextUtils.isEmpty(mPackageName)) {
+ Log.w(TAG, "getSelectedMediaDevice() package name is null or empty!");
+ return deviceList;
+ }
+
+ final RoutingSessionInfo info = getRoutingSessionInfo();
+ if (info != null) {
+ for (MediaRoute2Info route : mRouterManager.getSelectedRoutes(info)) {
+ deviceList.add(new InfoMediaDevice(mContext, mRouterManager,
+ route, mPackageName));
+ }
+ return deviceList;
+ }
+
+ Log.w(TAG, "getSelectedMediaDevice() cannot found selectable MediaDevice from : "
+ + mPackageName);
+
+ return deviceList;
+ }
+
+ void adjustSessionVolume(RoutingSessionInfo info, int volume) {
+ if (info == null) {
+ Log.w(TAG, "Unable to adjust session volume. RoutingSessionInfo is empty");
+ return;
+ }
+
+ mRouterManager.setSessionVolume(info, volume);
}
- class MediaRouterCallback extends MediaRouter.Callback {
+ /**
+ * Adjust the volume of {@link android.media.RoutingSessionInfo}.
+ *
+ * @param volume the value of volume
+ */
+ void adjustSessionVolume(int volume) {
+ if (TextUtils.isEmpty(mPackageName)) {
+ Log.w(TAG, "adjustSessionVolume() package name is null or empty!");
+ return;
+ }
+
+ final RoutingSessionInfo info = getRoutingSessionInfo();
+ if (info != null) {
+ Log.d(TAG, "adjustSessionVolume() adjust volume : " + volume + ", with : "
+ + mPackageName);
+ mRouterManager.setSessionVolume(info, volume);
+ return;
+ }
+
+ Log.w(TAG, "adjustSessionVolume() can't found corresponding RoutingSession with : "
+ + mPackageName);
+ }
+
+ /**
+ * Gets the maximum volume of the {@link android.media.RoutingSessionInfo}.
+ *
+ * @return maximum volume of the session, and return -1 if not found.
+ */
+ public int getSessionVolumeMax() {
+ if (TextUtils.isEmpty(mPackageName)) {
+ Log.w(TAG, "getSessionVolumeMax() package name is null or empty!");
+ return -1;
+ }
+
+ final RoutingSessionInfo info = getRoutingSessionInfo();
+ if (info != null) {
+ return info.getVolumeMax();
+ }
+
+ Log.w(TAG, "getSessionVolumeMax() can't found corresponding RoutingSession with : "
+ + mPackageName);
+ return -1;
+ }
+
+ /**
+ * Gets the current volume of the {@link android.media.RoutingSessionInfo}.
+ *
+ * @return current volume of the session, and return -1 if not found.
+ */
+ public int getSessionVolume() {
+ if (TextUtils.isEmpty(mPackageName)) {
+ Log.w(TAG, "getSessionVolume() package name is null or empty!");
+ return -1;
+ }
+
+ final RoutingSessionInfo info = getRoutingSessionInfo();
+ if (info != null) {
+ return info.getVolume();
+ }
+
+ Log.w(TAG, "getSessionVolume() can't found corresponding RoutingSession with : "
+ + mPackageName);
+ return -1;
+ }
+
+ CharSequence getSessionName() {
+ if (TextUtils.isEmpty(mPackageName)) {
+ Log.w(TAG, "Unable to get session name. The package name is null or empty!");
+ return null;
+ }
+
+ final RoutingSessionInfo info = getRoutingSessionInfo();
+ if (info != null) {
+ return info.getName();
+ }
+
+ Log.w(TAG, "Unable to get session name for package: " + mPackageName);
+ return null;
+ }
+
+ private void refreshDevices() {
+ mMediaDevices.clear();
+ mCurrentConnectedDevice = null;
+ if (TextUtils.isEmpty(mPackageName)) {
+ buildAllRoutes();
+ } else {
+ buildAvailableRoutes();
+ }
+ dispatchDeviceListAdded();
+ }
+
+ private void buildAllRoutes() {
+ for (MediaRoute2Info route : mRouterManager.getAllRoutes()) {
+ if (DEBUG) {
+ Log.d(TAG, "buildAllRoutes() route : " + route.getName() + ", volume : "
+ + route.getVolume() + ", type : " + route.getType());
+ }
+ if (route.isSystemRoute()) {
+ addMediaDevice(route);
+ }
+ }
+ }
+
+ List<RoutingSessionInfo> getActiveMediaSession() {
+ return mRouterManager.getActiveSessions();
+ }
+
+ private void buildAvailableRoutes() {
+ for (MediaRoute2Info route : mRouterManager.getAvailableRoutes(mPackageName)) {
+ if (DEBUG) {
+ Log.d(TAG, "buildAvailableRoutes() route : " + route.getName() + ", volume : "
+ + route.getVolume() + ", type : " + route.getType());
+ }
+ addMediaDevice(route);
+ }
+ }
+
+ @VisibleForTesting
+ void addMediaDevice(MediaRoute2Info route) {
+ final int deviceType = route.getType();
+ MediaDevice mediaDevice = null;
+ switch (deviceType) {
+ case TYPE_UNKNOWN:
+ case TYPE_REMOTE_TV:
+ case TYPE_REMOTE_SPEAKER:
+ case TYPE_GROUP:
+ //TODO(b/148765806): use correct device type once api is ready.
+ mediaDevice = new InfoMediaDevice(mContext, mRouterManager, route,
+ mPackageName);
+ if (!TextUtils.isEmpty(mPackageName)
+ && getRoutingSessionInfo().getSelectedRoutes().contains(route.getId())
+ && mCurrentConnectedDevice == null) {
+ mCurrentConnectedDevice = mediaDevice;
+ }
+ break;
+ case TYPE_BUILTIN_SPEAKER:
+ case TYPE_USB_DEVICE:
+ case TYPE_USB_HEADSET:
+ case TYPE_USB_ACCESSORY:
+ case TYPE_DOCK:
+ case TYPE_HDMI:
+ case TYPE_WIRED_HEADSET:
+ case TYPE_WIRED_HEADPHONES:
+ mediaDevice =
+ new PhoneMediaDevice(mContext, mRouterManager, route, mPackageName);
+ break;
+ case TYPE_HEARING_AID:
+ case TYPE_BLUETOOTH_A2DP:
+ final BluetoothDevice device =
+ BluetoothAdapter.getDefaultAdapter().getRemoteDevice(route.getAddress());
+ final CachedBluetoothDevice cachedDevice =
+ mBluetoothManager.getCachedDeviceManager().findDevice(device);
+ if (cachedDevice != null) {
+ mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice, mRouterManager,
+ route, mPackageName);
+ }
+ break;
+ default:
+ Log.w(TAG, "addMediaDevice() unknown device type : " + deviceType);
+ break;
+
+ }
+
+ if (mediaDevice != null) {
+ mMediaDevices.add(mediaDevice);
+ }
+ }
+
+ class RouterManagerCallback extends MediaRouter2Manager.Callback {
+
@Override
- public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo route) {
- MediaDevice mediaDevice = findMediaDevice(MediaDeviceUtils.getId(route));
- if (mediaDevice == null) {
- mediaDevice = new InfoMediaDevice(mContext, route);
- Log.d(TAG, "onRouteAdded() route : " + route.getName());
- mMediaDevices.add(mediaDevice);
- dispatchDeviceAdded(mediaDevice);
+ public void onRoutesAdded(List<MediaRoute2Info> routes) {
+ refreshDevices();
+ }
+
+ @Override
+ public void onPreferredFeaturesChanged(String packageName, List<String> preferredFeatures) {
+ if (TextUtils.equals(mPackageName, packageName)) {
+ refreshDevices();
}
}
@Override
- public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo route) {
- final MediaDevice mediaDevice = findMediaDevice(MediaDeviceUtils.getId(route));
- if (mediaDevice != null) {
- Log.d(TAG, "onRouteRemoved() route : " + route.getName());
- mMediaDevices.remove(mediaDevice);
- dispatchDeviceRemoved(mediaDevice);
+ public void onRoutesChanged(List<MediaRoute2Info> routes) {
+ refreshDevices();
+ }
+
+ @Override
+ public void onRoutesRemoved(List<MediaRoute2Info> routes) {
+ refreshDevices();
+ }
+
+ @Override
+ public void onTransferred(RoutingSessionInfo oldSession, RoutingSessionInfo newSession) {
+ if (DEBUG) {
+ Log.d(TAG, "onTransferred() oldSession : " + oldSession.getName()
+ + ", newSession : " + newSession.getName());
+ }
+ mMediaDevices.clear();
+ mCurrentConnectedDevice = null;
+ if (TextUtils.isEmpty(mPackageName)) {
+ buildAllRoutes();
+ } else {
+ buildAvailableRoutes();
}
+
+ final String id = mCurrentConnectedDevice != null
+ ? mCurrentConnectedDevice.getId()
+ : null;
+ dispatchConnectedDeviceChanged(id);
+ }
+
+ @Override
+ public void onTransferFailed(RoutingSessionInfo session, MediaRoute2Info route) {
+ dispatchOnRequestFailed(REASON_UNKNOWN_ERROR);
+ }
+
+ @Override
+ public void onRequestFailed(int reason) {
+ dispatchOnRequestFailed(reason);
+ }
+
+ @Override
+ public void onSessionUpdated(RoutingSessionInfo sessionInfo) {
+ dispatchDataChanged();
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 56b14c6b652d..9d06c8467e41 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -15,17 +15,27 @@
*/
package com.android.settingslib.media;
+import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
+
import android.app.Notification;
-import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
import android.content.Context;
+import android.media.RoutingSessionInfo;
+import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.bluetooth.A2dpProfile;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.HearingAidProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfile;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -34,6 +44,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* LocalMediaManager provide interface to get MediaDevice list and transfer media to MediaDevice.
@@ -41,112 +52,143 @@ import java.util.List;
public class LocalMediaManager implements BluetoothCallback {
private static final Comparator<MediaDevice> COMPARATOR = Comparator.naturalOrder();
private static final String TAG = "LocalMediaManager";
+ private static final int MAX_DISCONNECTED_DEVICE_NUM = 5;
@Retention(RetentionPolicy.SOURCE)
@IntDef({MediaDeviceState.STATE_CONNECTED,
MediaDeviceState.STATE_CONNECTING,
- MediaDeviceState.STATE_DISCONNECTED})
+ MediaDeviceState.STATE_DISCONNECTED,
+ MediaDeviceState.STATE_CONNECTING_FAILED})
public @interface MediaDeviceState {
- int STATE_CONNECTED = 1;
- int STATE_CONNECTING = 2;
- int STATE_DISCONNECTED = 3;
+ int STATE_CONNECTED = 0;
+ int STATE_CONNECTING = 1;
+ int STATE_DISCONNECTED = 2;
+ int STATE_CONNECTING_FAILED = 3;
}
- private final Collection<DeviceCallback> mCallbacks = new ArrayList<>();
+ private final Collection<DeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
+ private final Object mMediaDevicesLock = new Object();
@VisibleForTesting
final MediaDeviceCallback mMediaDeviceCallback = new MediaDeviceCallback();
private Context mContext;
- private BluetoothMediaManager mBluetoothMediaManager;
private LocalBluetoothManager mLocalBluetoothManager;
+ private InfoMediaManager mInfoMediaManager;
+ private String mPackageName;
+ private MediaDevice mOnTransferBluetoothDevice;
@VisibleForTesting
- List<MediaDevice> mMediaDevices = new ArrayList<>();
+ List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
+ @VisibleForTesting
+ List<MediaDevice> mDisconnectedMediaDevices = new CopyOnWriteArrayList<>();
@VisibleForTesting
MediaDevice mPhoneDevice;
@VisibleForTesting
MediaDevice mCurrentConnectedDevice;
+ @VisibleForTesting
+ DeviceAttributeChangeCallback mDeviceAttributeChangeCallback =
+ new DeviceAttributeChangeCallback();
+ @VisibleForTesting
+ BluetoothAdapter mBluetoothAdapter;
/**
* Register to start receiving callbacks for MediaDevice events.
*/
public void registerCallback(DeviceCallback callback) {
- synchronized (mCallbacks) {
- mCallbacks.add(callback);
- }
+ mCallbacks.add(callback);
}
/**
* Unregister to stop receiving callbacks for MediaDevice events
*/
public void unregisterCallback(DeviceCallback callback) {
- synchronized (mCallbacks) {
- mCallbacks.remove(callback);
- }
+ mCallbacks.remove(callback);
}
+ /**
+ * Creates a LocalMediaManager with references to given managers.
+ *
+ * It will obtain a {@link LocalBluetoothManager} by calling
+ * {@link LocalBluetoothManager#getInstance} and create an {@link InfoMediaManager} passing
+ * that bluetooth manager.
+ *
+ * It will use {@link BluetoothAdapter#getDefaultAdapter()] for setting the bluetooth adapter.
+ */
public LocalMediaManager(Context context, String packageName, Notification notification) {
mContext = context;
+ mPackageName = packageName;
mLocalBluetoothManager =
LocalBluetoothManager.getInstance(context, /* onInitCallback= */ null);
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mLocalBluetoothManager == null) {
Log.e(TAG, "Bluetooth is not supported on this device");
return;
}
- mBluetoothMediaManager =
- new BluetoothMediaManager(context, mLocalBluetoothManager, notification);
+ mInfoMediaManager =
+ new InfoMediaManager(context, packageName, notification, mLocalBluetoothManager);
}
- @VisibleForTesting
- LocalMediaManager(Context context, LocalBluetoothManager localBluetoothManager,
- BluetoothMediaManager bluetoothMediaManager, InfoMediaManager infoMediaManager) {
+ /**
+ * Creates a LocalMediaManager with references to given managers.
+ *
+ * It will use {@link BluetoothAdapter#getDefaultAdapter()] for setting the bluetooth adapter.
+ */
+ public LocalMediaManager(Context context, LocalBluetoothManager localBluetoothManager,
+ InfoMediaManager infoMediaManager, String packageName) {
mContext = context;
mLocalBluetoothManager = localBluetoothManager;
- mBluetoothMediaManager = bluetoothMediaManager;
+ mInfoMediaManager = infoMediaManager;
+ mPackageName = packageName;
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
/**
* Connect the MediaDevice to transfer media
* @param connectDevice the MediaDevice
+ * @return {@code true} if successfully call, otherwise return {@code false}
*/
- public void connectDevice(MediaDevice connectDevice) {
- final MediaDevice device = getMediaDeviceById(mMediaDevices, connectDevice.getId());
+ public boolean connectDevice(MediaDevice connectDevice) {
+ MediaDevice device = null;
+ synchronized (mMediaDevicesLock) {
+ device = getMediaDeviceById(mMediaDevices, connectDevice.getId());
+ }
+ if (device == null) {
+ Log.w(TAG, "connectDevice() connectDevice not in the list!");
+ return false;
+ }
if (device instanceof BluetoothMediaDevice) {
final CachedBluetoothDevice cachedDevice =
((BluetoothMediaDevice) device).getCachedDevice();
if (!cachedDevice.isConnected() && !cachedDevice.isBusy()) {
+ mOnTransferBluetoothDevice = connectDevice;
+ device.setState(MediaDeviceState.STATE_CONNECTING);
cachedDevice.connect();
- return;
+ return true;
}
}
if (device == mCurrentConnectedDevice) {
Log.d(TAG, "connectDevice() this device all ready connected! : " + device.getName());
- return;
+ return false;
}
- //TODO(b/121083246): Update it once remote media API is ready.
- if (mCurrentConnectedDevice != null && !(connectDevice instanceof InfoMediaDevice)) {
+ if (mCurrentConnectedDevice != null) {
mCurrentConnectedDevice.disconnect();
}
- final boolean isConnected = device.connect();
- if (isConnected) {
- mCurrentConnectedDevice = device;
+ device.setState(MediaDeviceState.STATE_CONNECTING);
+ if (TextUtils.isEmpty(mPackageName)) {
+ mInfoMediaManager.connectDeviceWithoutPackageName(device);
+ } else {
+ device.connect();
}
-
- final int state = isConnected
- ? MediaDeviceState.STATE_CONNECTED
- : MediaDeviceState.STATE_DISCONNECTED;
- dispatchSelectedDeviceStateChanged(device, state);
+ return true;
}
void dispatchSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state) {
- synchronized (mCallbacks) {
- for (DeviceCallback callback : mCallbacks) {
- callback.onSelectedDeviceStateChanged(device, state);
- }
+ for (DeviceCallback callback : getCallbacks()) {
+ callback.onSelectedDeviceStateChanged(device, state);
}
}
@@ -154,34 +196,30 @@ public class LocalMediaManager implements BluetoothCallback {
* Start scan connected MediaDevice
*/
public void startScan() {
- mMediaDevices.clear();
- mBluetoothMediaManager.registerCallback(mMediaDeviceCallback);
- mBluetoothMediaManager.startScan();
+ synchronized (mMediaDevicesLock) {
+ mMediaDevices.clear();
+ }
+ mInfoMediaManager.registerCallback(mMediaDeviceCallback);
+ mInfoMediaManager.startScan();
}
- private void addPhoneDeviceIfNecessary() {
- // add phone device to list if there have any Bluetooth device and cast device.
- if (mMediaDevices.size() > 0 && !mMediaDevices.contains(mPhoneDevice)) {
- if (mPhoneDevice == null) {
- mPhoneDevice = new PhoneMediaDevice(mContext, mLocalBluetoothManager);
- }
- mMediaDevices.add(mPhoneDevice);
+ void dispatchDeviceListUpdate() {
+ final List<MediaDevice> mediaDevices = new ArrayList<>(mMediaDevices);
+ Collections.sort(mediaDevices, COMPARATOR);
+ for (DeviceCallback callback : getCallbacks()) {
+ callback.onDeviceListUpdate(mediaDevices);
}
}
- private void removePhoneMediaDeviceIfNecessary() {
- // if PhoneMediaDevice is the last item in the list, remove it.
- if (mMediaDevices.size() == 1 && mMediaDevices.contains(mPhoneDevice)) {
- mMediaDevices.clear();
+ void dispatchDeviceAttributesChanged() {
+ for (DeviceCallback callback : getCallbacks()) {
+ callback.onDeviceAttributesChanged();
}
}
- void dispatchDeviceListUpdate() {
- synchronized (mCallbacks) {
- Collections.sort(mMediaDevices, COMPARATOR);
- for (DeviceCallback callback : mCallbacks) {
- callback.onDeviceListUpdate(new ArrayList<>(mMediaDevices));
- }
+ void dispatchOnRequestFailed(int reason) {
+ for (DeviceCallback callback : getCallbacks()) {
+ callback.onRequestFailed(reason);
}
}
@@ -189,8 +227,9 @@ public class LocalMediaManager implements BluetoothCallback {
* Stop scan MediaDevice
*/
public void stopScan() {
- mBluetoothMediaManager.unregisterCallback(mMediaDeviceCallback);
- mBluetoothMediaManager.stopScan();
+ mInfoMediaManager.unregisterCallback(mMediaDeviceCallback);
+ mInfoMediaManager.stopScan();
+ unRegisterDeviceAttributeChangeCallback();
}
/**
@@ -202,7 +241,7 @@ public class LocalMediaManager implements BluetoothCallback {
*/
public MediaDevice getMediaDeviceById(List<MediaDevice> devices, String id) {
for (MediaDevice mediaDevice : devices) {
- if (mediaDevice.getId().equals(id)) {
+ if (TextUtils.equals(mediaDevice.getId(), id)) {
return mediaDevice;
}
}
@@ -211,97 +250,353 @@ public class LocalMediaManager implements BluetoothCallback {
}
/**
+ * Find the MediaDevice from all media devices by id.
+ *
+ * @param id the unique id of MediaDevice
+ * @return MediaDevice
+ */
+ public MediaDevice getMediaDeviceById(String id) {
+ synchronized (mMediaDevicesLock) {
+ for (MediaDevice mediaDevice : mMediaDevices) {
+ if (TextUtils.equals(mediaDevice.getId(), id)) {
+ return mediaDevice;
+ }
+ }
+ }
+ Log.i(TAG, "Unable to find device " + id);
+ return null;
+ }
+
+ /**
* Find the current connected MediaDevice.
*
* @return MediaDevice
*/
+ @Nullable
public MediaDevice getCurrentConnectedDevice() {
return mCurrentConnectedDevice;
}
- private MediaDevice updateCurrentConnectedDevice() {
- for (MediaDevice device : mMediaDevices) {
- if (device instanceof BluetoothMediaDevice) {
- if (isConnected(((BluetoothMediaDevice) device).getCachedDevice())) {
- return device;
+ /**
+ * Add a MediaDevice to let it play current media.
+ *
+ * @param device MediaDevice
+ * @return If add device successful return {@code true}, otherwise return {@code false}
+ */
+ public boolean addDeviceToPlayMedia(MediaDevice device) {
+ return mInfoMediaManager.addDeviceToPlayMedia(device);
+ }
+
+ /**
+ * Remove a {@code device} from current media.
+ *
+ * @param device MediaDevice
+ * @return If device stop successful return {@code true}, otherwise return {@code false}
+ */
+ public boolean removeDeviceFromPlayMedia(MediaDevice device) {
+ return mInfoMediaManager.removeDeviceFromPlayMedia(device);
+ }
+
+ /**
+ * Get the MediaDevice list that can be added to current media.
+ *
+ * @return list of MediaDevice
+ */
+ public List<MediaDevice> getSelectableMediaDevice() {
+ return mInfoMediaManager.getSelectableMediaDevice();
+ }
+
+ /**
+ * Get the MediaDevice list that can be removed from current media session.
+ *
+ * @return list of MediaDevice
+ */
+ public List<MediaDevice> getDeselectableMediaDevice() {
+ return mInfoMediaManager.getDeselectableMediaDevice();
+ }
+
+ /**
+ * Release session to stop playing media on MediaDevice.
+ */
+ public boolean releaseSession() {
+ return mInfoMediaManager.releaseSession();
+ }
+
+ /**
+ * Get the MediaDevice list that has been selected to current media.
+ *
+ * @return list of MediaDevice
+ */
+ public List<MediaDevice> getSelectedMediaDevice() {
+ return mInfoMediaManager.getSelectedMediaDevice();
+ }
+
+ /**
+ * Adjust the volume of session.
+ *
+ * @param sessionId the value of media session id
+ * @param volume the value of volume
+ */
+ public void adjustSessionVolume(String sessionId, int volume) {
+ final List<RoutingSessionInfo> infos = getActiveMediaSession();
+ for (RoutingSessionInfo info : infos) {
+ if (TextUtils.equals(sessionId, info.getId())) {
+ mInfoMediaManager.adjustSessionVolume(info, volume);
+ return;
+ }
+ }
+ Log.w(TAG, "adjustSessionVolume: Unable to find session: " + sessionId);
+ }
+
+ /**
+ * Adjust the volume of session.
+ *
+ * @param volume the value of volume
+ */
+ public void adjustSessionVolume(int volume) {
+ mInfoMediaManager.adjustSessionVolume(volume);
+ }
+
+ /**
+ * Gets the maximum volume of the {@link android.media.RoutingSessionInfo}.
+ *
+ * @return maximum volume of the session, and return -1 if not found.
+ */
+ public int getSessionVolumeMax() {
+ return mInfoMediaManager.getSessionVolumeMax();
+ }
+
+ /**
+ * Gets the current volume of the {@link android.media.RoutingSessionInfo}.
+ *
+ * @return current volume of the session, and return -1 if not found.
+ */
+ public int getSessionVolume() {
+ return mInfoMediaManager.getSessionVolume();
+ }
+
+ /**
+ * Gets the user-visible name of the {@link android.media.RoutingSessionInfo}.
+ *
+ * @return current name of the session, and return {@code null} if not found.
+ */
+ public CharSequence getSessionName() {
+ return mInfoMediaManager.getSessionName();
+ }
+
+ /**
+ * Gets the current active session.
+ *
+ * @return current active session list{@link android.media.RoutingSessionInfo}
+ */
+ public List<RoutingSessionInfo> getActiveMediaSession() {
+ return mInfoMediaManager.getActiveMediaSession();
+ }
+
+ /**
+ * Gets the current package name.
+ *
+ * @return current package name
+ */
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ @VisibleForTesting
+ MediaDevice updateCurrentConnectedDevice() {
+ MediaDevice connectedDevice = null;
+ synchronized (mMediaDevicesLock) {
+ for (MediaDevice device : mMediaDevices) {
+ if (device instanceof BluetoothMediaDevice) {
+ if (isActiveDevice(((BluetoothMediaDevice) device).getCachedDevice())
+ && device.isConnected()) {
+ return device;
+ }
+ } else if (device instanceof PhoneMediaDevice) {
+ connectedDevice = device;
}
}
}
- return mMediaDevices.contains(mPhoneDevice) ? mPhoneDevice : null;
+
+ return connectedDevice;
+ }
+
+ private boolean isActiveDevice(CachedBluetoothDevice device) {
+ boolean isActiveDeviceA2dp = false;
+ boolean isActiveDeviceHearingAid = false;
+ final A2dpProfile a2dpProfile = mLocalBluetoothManager.getProfileManager().getA2dpProfile();
+ if (a2dpProfile != null) {
+ isActiveDeviceA2dp = device.getDevice().equals(a2dpProfile.getActiveDevice());
+ }
+ if (!isActiveDeviceA2dp) {
+ final HearingAidProfile hearingAidProfile = mLocalBluetoothManager.getProfileManager()
+ .getHearingAidProfile();
+ if (hearingAidProfile != null) {
+ isActiveDeviceHearingAid =
+ hearingAidProfile.getActiveDevices().contains(device.getDevice());
+ }
+ }
+
+ return isActiveDeviceA2dp || isActiveDeviceHearingAid;
}
- private boolean isConnected(CachedBluetoothDevice device) {
- return device.isActiveDevice(BluetoothProfile.A2DP)
- || device.isActiveDevice(BluetoothProfile.HEARING_AID);
+ private Collection<DeviceCallback> getCallbacks() {
+ return new CopyOnWriteArrayList<>(mCallbacks);
}
class MediaDeviceCallback implements MediaManager.MediaDeviceCallback {
@Override
public void onDeviceAdded(MediaDevice device) {
- if (!mMediaDevices.contains(device)) {
- mMediaDevices.add(device);
- addPhoneDeviceIfNecessary();
+ boolean isAdded = false;
+ synchronized (mMediaDevicesLock) {
+ if (!mMediaDevices.contains(device)) {
+ mMediaDevices.add(device);
+ isAdded = true;
+ }
+ }
+
+ if (isAdded) {
dispatchDeviceListUpdate();
}
}
@Override
public void onDeviceListAdded(List<MediaDevice> devices) {
- for (MediaDevice device : devices) {
- if (getMediaDeviceById(mMediaDevices, device.getId()) == null) {
- mMediaDevices.add(device);
- }
+ synchronized (mMediaDevicesLock) {
+ mMediaDevices.clear();
+ mMediaDevices.addAll(devices);
+ mMediaDevices.addAll(buildDisconnectedBluetoothDevice());
}
- addPhoneDeviceIfNecessary();
- mCurrentConnectedDevice = updateCurrentConnectedDevice();
- updatePhoneMediaDeviceSummary();
+
+ final MediaDevice infoMediaDevice = mInfoMediaManager.getCurrentConnectedDevice();
+ mCurrentConnectedDevice = infoMediaDevice != null
+ ? infoMediaDevice : updateCurrentConnectedDevice();
dispatchDeviceListUpdate();
+ if (mOnTransferBluetoothDevice != null && mOnTransferBluetoothDevice.isConnected()) {
+ connectDevice(mOnTransferBluetoothDevice);
+ mOnTransferBluetoothDevice.setState(MediaDeviceState.STATE_CONNECTED);
+ dispatchSelectedDeviceStateChanged(mOnTransferBluetoothDevice,
+ MediaDeviceState.STATE_CONNECTED);
+ mOnTransferBluetoothDevice = null;
+ }
+ }
+
+ private List<MediaDevice> buildDisconnectedBluetoothDevice() {
+ if (mBluetoothAdapter == null) {
+ Log.w(TAG, "buildDisconnectedBluetoothDevice() BluetoothAdapter is null");
+ return new ArrayList<>();
+ }
+
+ final List<BluetoothDevice> bluetoothDevices =
+ mBluetoothAdapter.getMostRecentlyConnectedDevices();
+ final CachedBluetoothDeviceManager cachedDeviceManager =
+ mLocalBluetoothManager.getCachedDeviceManager();
+
+ final List<CachedBluetoothDevice> cachedBluetoothDeviceList = new ArrayList<>();
+ int deviceCount = 0;
+ for (BluetoothDevice device : bluetoothDevices) {
+ final CachedBluetoothDevice cachedDevice =
+ cachedDeviceManager.findDevice(device);
+ if (cachedDevice != null) {
+ if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED
+ && !cachedDevice.isConnected()
+ && isA2dpOrHearingAidDevice(cachedDevice)) {
+ deviceCount++;
+ cachedBluetoothDeviceList.add(cachedDevice);
+ if (deviceCount >= MAX_DISCONNECTED_DEVICE_NUM) {
+ break;
+ }
+ }
+ }
+ }
+
+ unRegisterDeviceAttributeChangeCallback();
+ mDisconnectedMediaDevices.clear();
+ for (CachedBluetoothDevice cachedDevice : cachedBluetoothDeviceList) {
+ final MediaDevice mediaDevice = new BluetoothMediaDevice(mContext,
+ cachedDevice,
+ null, null, mPackageName);
+ if (!mMediaDevices.contains(mediaDevice)) {
+ cachedDevice.registerCallback(mDeviceAttributeChangeCallback);
+ mDisconnectedMediaDevices.add(mediaDevice);
+ }
+ }
+ return new ArrayList<>(mDisconnectedMediaDevices);
}
- private void updatePhoneMediaDeviceSummary() {
- if (mPhoneDevice != null) {
- ((PhoneMediaDevice) mPhoneDevice)
- .updateSummary(mCurrentConnectedDevice == mPhoneDevice);
+ private boolean isA2dpOrHearingAidDevice(CachedBluetoothDevice device) {
+ for (LocalBluetoothProfile profile : device.getConnectableProfiles()) {
+ if (profile instanceof A2dpProfile || profile instanceof HearingAidProfile) {
+ return true;
+ }
}
+ return false;
}
@Override
public void onDeviceRemoved(MediaDevice device) {
- if (mMediaDevices.contains(device)) {
- mMediaDevices.remove(device);
- removePhoneMediaDeviceIfNecessary();
+ boolean isRemoved = false;
+ synchronized (mMediaDevicesLock) {
+ if (mMediaDevices.contains(device)) {
+ mMediaDevices.remove(device);
+ isRemoved = true;
+ }
+ }
+ if (isRemoved) {
dispatchDeviceListUpdate();
}
}
@Override
public void onDeviceListRemoved(List<MediaDevice> devices) {
- mMediaDevices.removeAll(devices);
- removePhoneMediaDeviceIfNecessary();
+ synchronized (mMediaDevicesLock) {
+ mMediaDevices.removeAll(devices);
+ }
dispatchDeviceListUpdate();
}
@Override
public void onConnectedDeviceChanged(String id) {
- final MediaDevice connectDevice = getMediaDeviceById(mMediaDevices, id);
-
- if (connectDevice == mCurrentConnectedDevice) {
- Log.d(TAG, "onConnectedDeviceChanged() this device all ready connected!");
- return;
+ MediaDevice connectDevice = null;
+ synchronized (mMediaDevicesLock) {
+ connectDevice = getMediaDeviceById(mMediaDevices, id);
}
+ connectDevice = connectDevice != null
+ ? connectDevice : updateCurrentConnectedDevice();
+
mCurrentConnectedDevice = connectDevice;
- updatePhoneMediaDeviceSummary();
- dispatchDeviceListUpdate();
+ if (connectDevice != null) {
+ connectDevice.setState(MediaDeviceState.STATE_CONNECTED);
+
+ dispatchSelectedDeviceStateChanged(mCurrentConnectedDevice,
+ MediaDeviceState.STATE_CONNECTED);
+ }
}
@Override
public void onDeviceAttributesChanged() {
- addPhoneDeviceIfNecessary();
- removePhoneMediaDeviceIfNecessary();
- dispatchDeviceListUpdate();
+ dispatchDeviceAttributesChanged();
+ }
+
+ @Override
+ public void onRequestFailed(int reason) {
+ synchronized (mMediaDevicesLock) {
+ for (MediaDevice device : mMediaDevices) {
+ if (device.getState() == MediaDeviceState.STATE_CONNECTING) {
+ device.setState(MediaDeviceState.STATE_CONNECTING_FAILED);
+ }
+ }
+ }
+ dispatchOnRequestFailed(reason);
}
}
+ private void unRegisterDeviceAttributeChangeCallback() {
+ for (MediaDevice device : mDisconnectedMediaDevices) {
+ ((BluetoothMediaDevice) device).getCachedDevice()
+ .unregisterCallback(mDeviceAttributeChangeCallback);
+ }
+ }
/**
* Callback for notifying device information updating
@@ -312,7 +607,7 @@ public class LocalMediaManager implements BluetoothCallback {
*
* @param devices MediaDevice list
*/
- void onDeviceListUpdate(List<MediaDevice> devices);
+ default void onDeviceListUpdate(List<MediaDevice> devices) {};
/**
* Callback for notifying the connected device is changed.
@@ -323,6 +618,46 @@ public class LocalMediaManager implements BluetoothCallback {
* {@link MediaDeviceState#STATE_CONNECTING},
* {@link MediaDeviceState#STATE_DISCONNECTED}
*/
- void onSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state);
+ default void onSelectedDeviceStateChanged(MediaDevice device,
+ @MediaDeviceState int state) {};
+
+ /**
+ * Callback for notifying the device attributes is changed.
+ */
+ default void onDeviceAttributesChanged() {};
+
+ /**
+ * Callback for notifying that transferring is failed.
+ *
+ * @param reason the reason that the request has failed. Can be one of followings:
+ * {@link android.media.MediaRoute2ProviderService#REASON_UNKNOWN_ERROR},
+ * {@link android.media.MediaRoute2ProviderService#REASON_REJECTED},
+ * {@link android.media.MediaRoute2ProviderService#REASON_NETWORK_ERROR},
+ * {@link android.media.MediaRoute2ProviderService#REASON_ROUTE_NOT_AVAILABLE},
+ * {@link android.media.MediaRoute2ProviderService#REASON_INVALID_COMMAND},
+ */
+ default void onRequestFailed(int reason){};
+ }
+
+ /**
+ * This callback is for update {@link BluetoothMediaDevice} summary when
+ * {@link CachedBluetoothDevice} connection state is changed.
+ */
+ @VisibleForTesting
+ class DeviceAttributeChangeCallback implements CachedBluetoothDevice.Callback {
+
+ @Override
+ public void onDeviceAttributesChanged() {
+ if (mOnTransferBluetoothDevice != null
+ && !((BluetoothMediaDevice) mOnTransferBluetoothDevice).getCachedDevice()
+ .isBusy()
+ && !mOnTransferBluetoothDevice.isConnected()) {
+ // Failed to connect
+ mOnTransferBluetoothDevice.setState(MediaDeviceState.STATE_CONNECTING_FAILED);
+ mOnTransferBluetoothDevice = null;
+ dispatchOnRequestFailed(REASON_UNKNOWN_ERROR);
+ }
+ dispatchDeviceAttributesChanged();
+ }
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 53a852069478..126f9b91b0d2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -15,11 +15,34 @@
*/
package com.android.settingslib.media;
+import static android.media.MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_DOCK;
+import static android.media.MediaRoute2Info.TYPE_GROUP;
+import static android.media.MediaRoute2Info.TYPE_HDMI;
+import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
+import static android.media.MediaRoute2Info.TYPE_UNKNOWN;
+import static android.media.MediaRoute2Info.TYPE_USB_ACCESSORY;
+import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
+import static android.media.MediaRoute2Info.TYPE_USB_HEADSET;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
+
import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
+import android.media.MediaRoute2Info;
+import android.media.MediaRouter2Manager;
import android.text.TextUtils;
import androidx.annotation.IntDef;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settingslib.R;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -31,23 +54,80 @@ public abstract class MediaDevice implements Comparable<MediaDevice> {
private static final String TAG = "MediaDevice";
@Retention(RetentionPolicy.SOURCE)
- @IntDef({MediaDeviceType.TYPE_CAST_DEVICE,
+ @IntDef({MediaDeviceType.TYPE_UNKNOWN,
+ MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE,
+ MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE,
+ MediaDeviceType.TYPE_FAST_PAIR_BLUETOOTH_DEVICE,
MediaDeviceType.TYPE_BLUETOOTH_DEVICE,
+ MediaDeviceType.TYPE_CAST_DEVICE,
+ MediaDeviceType.TYPE_CAST_GROUP_DEVICE,
MediaDeviceType.TYPE_PHONE_DEVICE})
public @interface MediaDeviceType {
- int TYPE_PHONE_DEVICE = 1;
- int TYPE_CAST_DEVICE = 2;
- int TYPE_BLUETOOTH_DEVICE = 3;
+ int TYPE_UNKNOWN = 0;
+ int TYPE_USB_C_AUDIO_DEVICE = 1;
+ int TYPE_3POINT5_MM_AUDIO_DEVICE = 2;
+ int TYPE_FAST_PAIR_BLUETOOTH_DEVICE = 3;
+ int TYPE_BLUETOOTH_DEVICE = 4;
+ int TYPE_CAST_DEVICE = 5;
+ int TYPE_CAST_GROUP_DEVICE = 6;
+ int TYPE_PHONE_DEVICE = 7;
}
+ @VisibleForTesting
+ int mType;
+
private int mConnectedRecord;
+ private int mState;
- protected Context mContext;
- protected int mType;
+ protected final Context mContext;
+ protected final MediaRoute2Info mRouteInfo;
+ protected final MediaRouter2Manager mRouterManager;
+ protected final String mPackageName;
- MediaDevice(Context context, @MediaDeviceType int type) {
- mType = type;
+ MediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
+ String packageName) {
mContext = context;
+ mRouteInfo = info;
+ mRouterManager = routerManager;
+ mPackageName = packageName;
+ setType(info);
+ }
+
+ private void setType(MediaRoute2Info info) {
+ if (info == null) {
+ mType = MediaDeviceType.TYPE_BLUETOOTH_DEVICE;
+ return;
+ }
+
+ switch (info.getType()) {
+ case TYPE_GROUP:
+ mType = MediaDeviceType.TYPE_CAST_GROUP_DEVICE;
+ break;
+ case TYPE_BUILTIN_SPEAKER:
+ mType = MediaDeviceType.TYPE_PHONE_DEVICE;
+ break;
+ case TYPE_WIRED_HEADSET:
+ case TYPE_WIRED_HEADPHONES:
+ mType = MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE;
+ break;
+ case TYPE_USB_DEVICE:
+ case TYPE_USB_HEADSET:
+ case TYPE_USB_ACCESSORY:
+ case TYPE_DOCK:
+ case TYPE_HDMI:
+ mType = MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE;
+ break;
+ case TYPE_HEARING_AID:
+ case TYPE_BLUETOOTH_A2DP:
+ mType = MediaDeviceType.TYPE_BLUETOOTH_DEVICE;
+ break;
+ case TYPE_UNKNOWN:
+ case TYPE_REMOTE_TV:
+ case TYPE_REMOTE_SPEAKER:
+ default:
+ mType = MediaDeviceType.TYPE_CAST_DEVICE;
+ break;
+ }
}
void initDeviceRecord() {
@@ -56,6 +136,14 @@ public abstract class MediaDevice implements Comparable<MediaDevice> {
getId());
}
+ void setColorFilter(Drawable drawable) {
+ final ColorStateList list =
+ mContext.getResources().getColorStateList(
+ R.color.advanced_icon_color, mContext.getTheme());
+ drawable.setColorFilter(new PorterDuffColorFilter(list.getDefaultColor(),
+ PorterDuff.Mode.SRC_IN));
+ }
+
/**
* Get name from MediaDevice.
*
@@ -78,48 +166,128 @@ public abstract class MediaDevice implements Comparable<MediaDevice> {
public abstract Drawable getIcon();
/**
+ * Get icon of MediaDevice without background.
+ *
+ * @return drawable of icon
+ */
+ public abstract Drawable getIconWithoutBackground();
+
+ /**
* Get unique ID that represent MediaDevice
* @return unique id of MediaDevice
*/
public abstract String getId();
+ void setConnectedRecord() {
+ mConnectedRecord++;
+ ConnectionRecordManager.getInstance().setConnectionRecord(mContext, getId(),
+ mConnectedRecord);
+ }
+
+ /**
+ * According the MediaDevice type to check whether we are connected to this MediaDevice.
+ *
+ * @return Whether it is connected.
+ */
+ public abstract boolean isConnected();
+
+ /**
+ * Request to set volume.
+ *
+ * @param volume is the new value.
+ */
+
+ public void requestSetVolume(int volume) {
+ mRouterManager.setRouteVolume(mRouteInfo, volume);
+ }
+
+ /**
+ * Get max volume from MediaDevice.
+ *
+ * @return max volume.
+ */
+ public int getMaxVolume() {
+ return mRouteInfo.getVolumeMax();
+ }
+
+ /**
+ * Get current volume from MediaDevice.
+ *
+ * @return current volume.
+ */
+ public int getCurrentVolume() {
+ return mRouteInfo.getVolume();
+ }
+
+ /**
+ * Get application package name.
+ *
+ * @return package name.
+ */
+ public String getClientPackageName() {
+ return mRouteInfo.getClientPackageName();
+ }
+
+ /**
+ * Get application label from MediaDevice.
+ *
+ * @return application label.
+ */
+ public int getDeviceType() {
+ return mType;
+ }
+
/**
* Transfer MediaDevice for media
*
* @return result of transfer media
*/
- public abstract boolean connect();
-
- void setConnectedRecord() {
- mConnectedRecord++;
- ConnectionRecordManager.getInstance().setConnectionRecord(mContext, getId(),
- mConnectedRecord);
+ public boolean connect() {
+ setConnectedRecord();
+ mRouterManager.selectRoute(mPackageName, mRouteInfo);
+ return true;
}
/**
* Stop transfer MediaDevice
*/
- public abstract void disconnect();
+ public void disconnect() {
+ }
/**
- * According the MediaDevice type to check whether we are connected to this MediaDevice.
+ * Set current device's state
+ */
+ public void setState(@LocalMediaManager.MediaDeviceState int state) {
+ mState = state;
+ }
+
+ /**
+ * Get current device's state
*
- * @return Whether it is connected.
+ * @return state of device
*/
- public abstract boolean isConnected();
+ public @LocalMediaManager.MediaDeviceState int getState() {
+ return mState;
+ }
/**
* Rules:
- * 1. If there is one of the connected devices identified as a carkit, this carkit will
- * be always on the top of the device list. Rule 2 and Rule 3 can’t overrule this rule.
+ * 1. If there is one of the connected devices identified as a carkit or fast pair device,
+ * the fast pair device will be always on the first of the device list and carkit will be
+ * second. Rule 2 and Rule 3 can’t overrule this rule.
* 2. For devices without any usage data yet
* WiFi device group sorted by alphabetical order + BT device group sorted by alphabetical
* order + phone speaker
* 3. For devices with usage record.
* The most recent used one + device group with usage info sorted by how many times the
* device has been used.
- * 4. Phone device always in the top and the connected Bluetooth devices, cast devices and
- * phone device will be always above on the disconnect Bluetooth devices.
+ * 4. The order is followed below rule:
+ * 1. USB-C audio device
+ * 2. 3.5 mm audio device
+ * 3. Bluetooth device
+ * 4. Cast device
+ * 5. Cast group device
+ * 6. Phone
*
* So the device list will look like 5 slots ranked as below.
* Rule 4 + Rule 1 + the most recently used device + Rule 3 + Rule 2
@@ -139,39 +307,50 @@ public abstract class MediaDevice implements Comparable<MediaDevice> {
}
}
- // Phone device always in the top.
- if (mType == MediaDeviceType.TYPE_PHONE_DEVICE) {
- return -1;
- } else if (another.mType == MediaDeviceType.TYPE_PHONE_DEVICE) {
- return 1;
- }
- // Check carkit
- if (isCarKitDevice()) {
- return -1;
- } else if (another.isCarKitDevice()) {
- return 1;
- }
- // Set last used device at the first item
- String lastSelectedDevice = ConnectionRecordManager.getInstance().getLastSelectedDevice();
- if (TextUtils.equals(lastSelectedDevice, getId())) {
- return -1;
- } else if (TextUtils.equals(lastSelectedDevice, another.getId())) {
- return 1;
- }
- // Sort by how many times the device has been used if there is usage record
- if ((mConnectedRecord != another.mConnectedRecord)
- && (another.mConnectedRecord > 0 || mConnectedRecord > 0)) {
- return (another.mConnectedRecord - mConnectedRecord);
- }
- // Both devices have never been used
- // To devices with the same type, sort by alphabetical order
if (mType == another.mType) {
+ // Check fast pair device
+ if (isFastPairDevice()) {
+ return -1;
+ } else if (another.isFastPairDevice()) {
+ return 1;
+ }
+
+ // Check carkit
+ if (isCarKitDevice()) {
+ return -1;
+ } else if (another.isCarKitDevice()) {
+ return 1;
+ }
+
+ // Set last used device at the first item
+ final String lastSelectedDevice = ConnectionRecordManager.getInstance()
+ .getLastSelectedDevice();
+ if (TextUtils.equals(lastSelectedDevice, getId())) {
+ return -1;
+ } else if (TextUtils.equals(lastSelectedDevice, another.getId())) {
+ return 1;
+ }
+ // Sort by how many times the device has been used if there is usage record
+ if ((mConnectedRecord != another.mConnectedRecord)
+ && (another.mConnectedRecord > 0 || mConnectedRecord > 0)) {
+ return (another.mConnectedRecord - mConnectedRecord);
+ }
+
+ // Both devices have never been used
+ // To devices with the same type, sort by alphabetical order
final String s1 = getName();
final String s2 = another.getName();
return s1.compareToIgnoreCase(s2);
+ } else {
+ // Both devices have never been used, the priority is:
+ // 1. USB-C audio device
+ // 2. 3.5 mm audio device
+ // 3. Bluetooth device
+ // 4. Cast device
+ // 5. Cast group device
+ // 6. Phone
+ return mType < another.mType ? -1 : 1;
}
- // Both devices have never been used, the priority is Phone > Cast > Bluetooth
- return mType - another.mType;
}
/**
@@ -182,6 +361,14 @@ public abstract class MediaDevice implements Comparable<MediaDevice> {
return false;
}
+ /**
+ * Check if it is FastPair device
+ * @return {@code true} if it is FastPair device, otherwise return {@code false}
+ */
+ protected boolean isFastPairDevice() {
+ return false;
+ }
+
@Override
public boolean equals(Object obj) {
if (!(obj instanceof MediaDevice)) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDeviceUtils.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDeviceUtils.java
index f181150de513..df6929e114ee 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDeviceUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDeviceUtils.java
@@ -16,8 +16,7 @@
package com.android.settingslib.media;
import android.bluetooth.BluetoothDevice;
-
-import androidx.mediarouter.media.MediaRouter;
+import android.media.MediaRoute2Info;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -32,6 +31,9 @@ public class MediaDeviceUtils {
* @return CachedBluetoothDevice address
*/
public static String getId(CachedBluetoothDevice cachedDevice) {
+ if (cachedDevice.isHearingAidDevice()) {
+ return Long.toString(cachedDevice.getHiSyncId());
+ }
return cachedDevice.getAddress();
}
@@ -46,12 +48,12 @@ public class MediaDeviceUtils {
}
/**
- * Use RouteInfo id to represent unique id
+ * Use MediaRoute2Info id to represent unique id
*
- * @param route the RouteInfo
- * @return RouteInfo id
+ * @param route the MediaRoute2Info
+ * @return MediaRoute2Info id
*/
- public static String getId(MediaRouter.RouteInfo route) {
+ public static String getId(MediaRoute2Info route) {
return route.getId();
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
index 7898982bccbc..e8cbab8197b2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
@@ -22,6 +22,7 @@ import android.util.Log;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* MediaManager provide interface to get MediaDevice list.
@@ -30,7 +31,7 @@ public abstract class MediaManager {
private static final String TAG = "MediaManager";
- protected final Collection<MediaDeviceCallback> mCallbacks = new ArrayList<>();
+ protected final Collection<MediaDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
protected final List<MediaDevice> mMediaDevices = new ArrayList<>();
protected Context mContext;
@@ -42,18 +43,14 @@ public abstract class MediaManager {
}
protected void registerCallback(MediaDeviceCallback callback) {
- synchronized (mCallbacks) {
- if (!mCallbacks.contains(callback)) {
- mCallbacks.add(callback);
- }
+ if (!mCallbacks.contains(callback)) {
+ mCallbacks.add(callback);
}
}
protected void unregisterCallback(MediaDeviceCallback callback) {
- synchronized (mCallbacks) {
- if (mCallbacks.contains(callback)) {
- mCallbacks.remove(callback);
- }
+ if (mCallbacks.contains(callback)) {
+ mCallbacks.remove(callback);
}
}
@@ -78,53 +75,51 @@ public abstract class MediaManager {
}
protected void dispatchDeviceAdded(MediaDevice mediaDevice) {
- synchronized (mCallbacks) {
- for (MediaDeviceCallback callback : mCallbacks) {
- callback.onDeviceAdded(mediaDevice);
- }
+ for (MediaDeviceCallback callback : getCallbacks()) {
+ callback.onDeviceAdded(mediaDevice);
}
}
protected void dispatchDeviceRemoved(MediaDevice mediaDevice) {
- synchronized (mCallbacks) {
- for (MediaDeviceCallback callback : mCallbacks) {
- callback.onDeviceRemoved(mediaDevice);
- }
+ for (MediaDeviceCallback callback : getCallbacks()) {
+ callback.onDeviceRemoved(mediaDevice);
}
}
protected void dispatchDeviceListAdded() {
- synchronized (mCallbacks) {
- for (MediaDeviceCallback callback : mCallbacks) {
- callback.onDeviceListAdded(new ArrayList<>(mMediaDevices));
- }
+ for (MediaDeviceCallback callback : getCallbacks()) {
+ callback.onDeviceListAdded(new ArrayList<>(mMediaDevices));
}
}
protected void dispatchDeviceListRemoved(List<MediaDevice> devices) {
- synchronized (mCallbacks) {
- for (MediaDeviceCallback callback : mCallbacks) {
- callback.onDeviceListRemoved(devices);
- }
+ for (MediaDeviceCallback callback : getCallbacks()) {
+ callback.onDeviceListRemoved(devices);
}
}
protected void dispatchConnectedDeviceChanged(String id) {
- synchronized (mCallbacks) {
- for (MediaDeviceCallback callback : mCallbacks) {
- callback.onConnectedDeviceChanged(id);
- }
+ for (MediaDeviceCallback callback : getCallbacks()) {
+ callback.onConnectedDeviceChanged(id);
}
}
protected void dispatchDataChanged() {
- synchronized (mCallbacks) {
- for (MediaDeviceCallback callback : mCallbacks) {
- callback.onDeviceAttributesChanged();
- }
+ for (MediaDeviceCallback callback : getCallbacks()) {
+ callback.onDeviceAttributesChanged();
}
}
+ protected void dispatchOnRequestFailed(int reason) {
+ for (MediaDeviceCallback callback : getCallbacks()) {
+ callback.onRequestFailed(reason);
+ }
+ }
+
+ private Collection<MediaDeviceCallback> getCallbacks() {
+ return new CopyOnWriteArrayList<>(mCallbacks);
+ }
+
/**
* Callback for notifying device is added, removed and attributes changed.
*/
@@ -169,5 +164,17 @@ public abstract class MediaManager {
* (e.g: device name, connection state, subtitle) is changed.
*/
void onDeviceAttributesChanged();
+
+ /**
+ * Callback for notifying that transferring is failed.
+ *
+ * @param reason the reason that the request has failed. Can be one of followings:
+ * {@link android.media.MediaRoute2ProviderService#REASON_UNKNOWN_ERROR},
+ * {@link android.media.MediaRoute2ProviderService#REASON_REJECTED},
+ * {@link android.media.MediaRoute2ProviderService#REASON_NETWORK_ERROR},
+ * {@link android.media.MediaRoute2ProviderService#REASON_ROUTE_NOT_AVAILABLE},
+ * {@link android.media.MediaRoute2ProviderService#REASON_INVALID_COMMAND},
+ */
+ void onRequestFailed(int reason);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java
index e600cb892c44..2821af97ed98 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java
@@ -27,12 +27,39 @@ public class MediaOutputSliceConstants {
public static final String KEY_MEDIA_OUTPUT = "media_output";
/**
+ * Key for the Media output group setting.
+ */
+ public static final String KEY_MEDIA_OUTPUT_GROUP = "media_output_group";
+
+ /**
+ * Key for the Remote Media slice.
+ */
+ public static final String KEY_REMOTE_MEDIA = "remote_media";
+
+ /**
+ * Key for the {@link android.media.session.MediaSession.Token}.
+ */
+ public static final String KEY_MEDIA_SESSION_TOKEN = "key_media_session_token";
+
+ /**
+ * Key for the {@link android.media.RoutingSessionInfo#getId()}
+ */
+ public static final String KEY_SESSION_INFO_ID = "key_session_info_id";
+
+ /**
* Activity Action: Show a settings dialog containing {@link MediaDevice} to transfer media.
*/
public static final String ACTION_MEDIA_OUTPUT =
"com.android.settings.panel.action.MEDIA_OUTPUT";
/**
+ * Activity Action: Show a settings dialog containing {@link MediaDevice} to handle media group
+ * operation.
+ */
+ public static final String ACTION_MEDIA_OUTPUT_GROUP =
+ "com.android.settings.panel.action.MEDIA_OUTPUT_GROUP";
+
+ /**
* An string extra specifying a media package name.
*/
public static final String EXTRA_PACKAGE_NAME =
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index af91c3464194..b6c0b30b3bd4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -15,16 +15,24 @@
*/
package com.android.settingslib.media;
+import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_DOCK;
+import static android.media.MediaRoute2Info.TYPE_HDMI;
+import static android.media.MediaRoute2Info.TYPE_USB_ACCESSORY;
+import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
+import static android.media.MediaRoute2Info.TYPE_USB_HEADSET;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
+
import android.content.Context;
import android.graphics.drawable.Drawable;
-import android.util.Log;
+import android.media.MediaRoute2Info;
+import android.media.MediaRouter2Manager;
+
+import androidx.annotation.VisibleForTesting;
import com.android.settingslib.R;
-import com.android.settingslib.bluetooth.A2dpProfile;
import com.android.settingslib.bluetooth.BluetoothUtils;
-import com.android.settingslib.bluetooth.HearingAidProfile;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
/**
* PhoneMediaDevice extends MediaDevice to represents Phone device.
@@ -33,23 +41,41 @@ public class PhoneMediaDevice extends MediaDevice {
private static final String TAG = "PhoneMediaDevice";
- public static final String ID = "phone_media_device_id_1";
+ public static final String PHONE_ID = "phone_media_device_id";
+ // For 3.5 mm wired headset
+ public static final String WIRED_HEADSET_ID = "wired_headset_media_device_id";
+ public static final String USB_HEADSET_ID = "usb_headset_media_device_id";
- private LocalBluetoothProfileManager mProfileManager;
- private LocalBluetoothManager mLocalBluetoothManager;
private String mSummary = "";
- PhoneMediaDevice(Context context, LocalBluetoothManager localBluetoothManager) {
- super(context, MediaDeviceType.TYPE_PHONE_DEVICE);
+ PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
+ String packageName) {
+ super(context, routerManager, info, packageName);
- mLocalBluetoothManager = localBluetoothManager;
- mProfileManager = mLocalBluetoothManager.getProfileManager();
initDeviceRecord();
}
@Override
public String getName() {
- return mContext.getString(R.string.media_transfer_this_device_name);
+ CharSequence name;
+ switch (mRouteInfo.getType()) {
+ case TYPE_WIRED_HEADSET:
+ case TYPE_WIRED_HEADPHONES:
+ case TYPE_USB_DEVICE:
+ case TYPE_USB_HEADSET:
+ case TYPE_USB_ACCESSORY:
+ name = mContext.getString(R.string.media_transfer_wired_usb_device_name);
+ break;
+ case TYPE_DOCK:
+ case TYPE_HDMI:
+ name = mRouteInfo.getName();
+ break;
+ case TYPE_BUILTIN_SPEAKER:
+ default:
+ name = mContext.getString(R.string.media_transfer_this_device_name);
+ break;
+ }
+ return name.toString();
}
@Override
@@ -59,39 +85,58 @@ public class PhoneMediaDevice extends MediaDevice {
@Override
public Drawable getIcon() {
- return BluetoothUtils.buildBtRainbowDrawable(mContext,
- mContext.getDrawable(R.drawable.ic_smartphone), getId().hashCode());
+ final Drawable drawable = getIconWithoutBackground();
+ setColorFilter(drawable);
+ return BluetoothUtils.buildAdvancedDrawable(mContext, drawable);
}
@Override
- public String getId() {
- return ID;
+ public Drawable getIconWithoutBackground() {
+ return mContext.getDrawable(getDrawableResId());
}
- @Override
- public boolean connect() {
- final HearingAidProfile hapProfile = mProfileManager.getHearingAidProfile();
- final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile();
-
- // Some device may not have HearingAidProfile, consider all situation to set active device.
- boolean isConnected = false;
- if (hapProfile != null && a2dpProfile != null) {
- isConnected = hapProfile.setActiveDevice(null) && a2dpProfile.setActiveDevice(null);
- } else if (a2dpProfile != null) {
- isConnected = a2dpProfile.setActiveDevice(null);
- } else if (hapProfile != null) {
- isConnected = hapProfile.setActiveDevice(null);
+ @VisibleForTesting
+ int getDrawableResId() {
+ int resId;
+ switch (mRouteInfo.getType()) {
+ case TYPE_USB_DEVICE:
+ case TYPE_USB_HEADSET:
+ case TYPE_USB_ACCESSORY:
+ case TYPE_DOCK:
+ case TYPE_HDMI:
+ case TYPE_WIRED_HEADSET:
+ case TYPE_WIRED_HEADPHONES:
+ resId = R.drawable.ic_headphone;
+ break;
+ case TYPE_BUILTIN_SPEAKER:
+ default:
+ resId = R.drawable.ic_smartphone;
+ break;
}
- updateSummary(isConnected);
- setConnectedRecord();
-
- Log.d(TAG, "connect() device : " + getName() + ", is selected : " + isConnected);
- return isConnected;
+ return resId;
}
@Override
- public void disconnect() {
- updateSummary(false);
+ public String getId() {
+ String id;
+ switch (mRouteInfo.getType()) {
+ case TYPE_WIRED_HEADSET:
+ case TYPE_WIRED_HEADPHONES:
+ id = WIRED_HEADSET_ID;
+ break;
+ case TYPE_USB_DEVICE:
+ case TYPE_USB_HEADSET:
+ case TYPE_USB_ACCESSORY:
+ case TYPE_DOCK:
+ case TYPE_HDMI:
+ id = USB_HEADSET_ID;
+ break;
+ case TYPE_BUILTIN_SPEAKER:
+ default:
+ id = PHONE_ID;
+ break;
+ }
+ return id;
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
index 853c77efd4e9..b1234f291b74 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
@@ -18,11 +18,15 @@ package com.android.settingslib.net;
import android.content.Context;
import android.net.NetworkTemplate;
+import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;
import com.android.internal.util.ArrayUtils;
+
+import java.util.List;
+
/**
* Utils class for data usage
*/
@@ -33,26 +37,42 @@ public class DataUsageUtils {
* Return mobile NetworkTemplate based on {@code subId}
*/
public static NetworkTemplate getMobileTemplate(Context context, int subId) {
- final TelephonyManager telephonyManager = context.getSystemService(
- TelephonyManager.class);
- final SubscriptionManager subscriptionManager = context.getSystemService(
- SubscriptionManager.class);
- final NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll(
- telephonyManager.createForSubscriptionId(subId).getSubscriberId());
-
- if (!subscriptionManager.isActiveSubId(subId)) {
- Log.i(TAG, "Subscription is not active: " + subId);
- return mobileAll;
+ final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class);
+ final int mobileDefaultSubId = telephonyManager.getSubscriptionId();
+
+ final SubscriptionManager subscriptionManager =
+ context.getSystemService(SubscriptionManager.class);
+ final List<SubscriptionInfo> subInfoList =
+ subscriptionManager.getAvailableSubscriptionInfoList();
+ if (subInfoList == null) {
+ Log.i(TAG, "Subscription is not inited: " + subId);
+ return getMobileTemplateForSubId(telephonyManager, mobileDefaultSubId);
}
- final String[] mergedSubscriberIds = telephonyManager.createForSubscriptionId(subId)
- .getMergedImsisFromGroup();
+ for (SubscriptionInfo subInfo : subInfoList) {
+ if ((subInfo != null) && (subInfo.getSubscriptionId() == subId)) {
+ return getNormalizedMobileTemplate(telephonyManager, subId);
+ }
+ }
+ Log.i(TAG, "Subscription is not active: " + subId);
+ return getMobileTemplateForSubId(telephonyManager, mobileDefaultSubId);
+ }
+ private static NetworkTemplate getNormalizedMobileTemplate(
+ TelephonyManager telephonyManager, int subId) {
+ final NetworkTemplate mobileTemplate = getMobileTemplateForSubId(telephonyManager, subId);
+ final String[] mergedSubscriberIds = telephonyManager
+ .createForSubscriptionId(subId).getMergedImsisFromGroup();
if (ArrayUtils.isEmpty(mergedSubscriberIds)) {
Log.i(TAG, "mergedSubscriberIds is null.");
- return mobileAll;
+ return mobileTemplate;
}
- return NetworkTemplate.normalize(mobileAll, mergedSubscriberIds);
+ return NetworkTemplate.normalize(mobileTemplate, mergedSubscriberIds);
+ }
+
+ private static NetworkTemplate getMobileTemplateForSubId(
+ TelephonyManager telephonyManager, int subId) {
+ return NetworkTemplate.buildTemplateMobileAll(telephonyManager.getSubscriberId(subId));
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/UidDetailProvider.java b/packages/SettingsLib/src/com/android/settingslib/net/UidDetailProvider.java
index e3516158daac..dad82ee61e08 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/UidDetailProvider.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/UidDetailProvider.java
@@ -63,7 +63,7 @@ public class UidDetailProvider {
}
public UidDetailProvider(Context context) {
- mContext = context.getApplicationContext();
+ mContext = context;
mUidDetailCache = new SparseArray<UidDetail>();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
new file mode 100644
index 000000000000..549bc8a455cf
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2020 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.settingslib.notification;
+
+import android.annotation.ColorInt;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.util.IconDrawableFactory;
+import android.util.Log;
+
+import com.android.launcher3.icons.BaseIconFactory;
+import com.android.settingslib.R;
+
+/**
+ * Factory for creating normalized conversation icons.
+ * We are not using Launcher's IconFactory because conversation rendering only runs on the UI
+ * thread, so there is no need to manage a pool across multiple threads. Launcher's rendering
+ * also includes shadows, which are only appropriate on top of wallpaper, not embedded in UI.
+ */
+public class ConversationIconFactory extends BaseIconFactory {
+ // Geometry of the various parts of the design. All values are 1dp on a 56x56dp icon grid.
+ // Space is left around the "head" (main avatar) for
+ // ........
+ // .HHHHHH.
+ // .HHHrrrr
+ // .HHHrBBr
+ // ....rrrr
+ // This is trying to recreate the view layout in notification_template_material_conversation.xml
+
+ private static final float HEAD_SIZE = 52f;
+ private static final float BADGE_SIZE = 12f;
+ private static final float BADGE_CENTER = 46f;
+ private static final float CIRCLE_MARGIN = 36f;
+ private static final float BADGE_ORIGIN = HEAD_SIZE - BADGE_SIZE; // 40f
+ private static final float BASE_ICON_SIZE = 56f;
+
+ private static final float OUT_CIRCLE_DIA = (BASE_ICON_SIZE - CIRCLE_MARGIN); // 20f
+ private static final float INN_CIRCLE_DIA = (float) Math.sqrt(2 * BADGE_SIZE * BADGE_SIZE) ;
+ private static final float OUT_CIRCLE_RAD = OUT_CIRCLE_DIA / 2;
+ private static final float INN_CIRCLE_RAD = INN_CIRCLE_DIA / 2;
+ // Android draws strokes centered on the radius, so our actual radius is an avg of the outside
+ // and inside of the ring stroke
+ private static final float CIRCLE_RADIUS =
+ INN_CIRCLE_RAD + ((OUT_CIRCLE_RAD - INN_CIRCLE_RAD) / 2);
+ private static final float RING_STROKE_WIDTH = (OUT_CIRCLE_DIA - INN_CIRCLE_DIA) / 2;
+
+ final LauncherApps mLauncherApps;
+ final PackageManager mPackageManager;
+ final IconDrawableFactory mIconDrawableFactory;
+ private int mImportantConversationColor;
+
+ public ConversationIconFactory(Context context, LauncherApps la, PackageManager pm,
+ IconDrawableFactory iconDrawableFactory, int iconSizePx) {
+ super(context, context.getResources().getConfiguration().densityDpi,
+ iconSizePx);
+ mLauncherApps = la;
+ mPackageManager = pm;
+ mIconDrawableFactory = iconDrawableFactory;
+ mImportantConversationColor = context.getResources().getColor(
+ R.color.important_conversation, null);
+ }
+
+ /**
+ * Returns the conversation info drawable
+ */
+ public Drawable getBaseIconDrawable(ShortcutInfo shortcutInfo) {
+ return mLauncherApps.getShortcutIconDrawable(shortcutInfo, mFillResIconDpi);
+ }
+
+ /**
+ * Get the {@link Drawable} that represents the app icon, badged with the work profile icon
+ * if appropriate.
+ */
+ public Drawable getAppBadge(String packageName, int userId) {
+ Drawable badge = null;
+ try {
+ final ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser(
+ packageName, PackageManager.GET_META_DATA, userId);
+ badge = mIconDrawableFactory.getBadgedIcon(appInfo, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ badge = mPackageManager.getDefaultActivityIcon();
+ }
+ return badge;
+ }
+
+ /**
+ * Returns a {@link Drawable} for the entire conversation. The shortcut icon will be badged
+ * with the launcher icon of the app specified by packageName.
+ */
+ public Drawable getConversationDrawable(ShortcutInfo info, String packageName, int uid,
+ boolean important) {
+ return getConversationDrawable(getBaseIconDrawable(info), packageName, uid, important);
+ }
+
+ /**
+ * Returns a {@link Drawable} for the entire conversation. The drawable will be badged
+ * with the launcher icon of the app specified by packageName.
+ */
+ public Drawable getConversationDrawable(Drawable baseIcon, String packageName, int uid,
+ boolean important) {
+ return new ConversationIconDrawable(baseIcon,
+ getAppBadge(packageName, UserHandle.getUserId(uid)),
+ mIconBitmapSize,
+ mImportantConversationColor,
+ important);
+ }
+
+ /**
+ * Custom Drawable that overlays a badge drawable (e.g. notification small icon or app icon) on
+ * a base icon (conversation/person avatar), plus decorations indicating conversation
+ * importance.
+ */
+ public static class ConversationIconDrawable extends Drawable {
+ private Drawable mBaseIcon;
+ private Drawable mBadgeIcon;
+ private int mIconSize;
+ private Paint mRingPaint;
+ private boolean mShowRing;
+ private Paint mPaddingPaint;
+
+ public ConversationIconDrawable(Drawable baseIcon,
+ Drawable badgeIcon,
+ int iconSize,
+ @ColorInt int ringColor,
+ boolean showImportanceRing) {
+ mBaseIcon = baseIcon;
+ mBadgeIcon = badgeIcon;
+ mIconSize = iconSize;
+ mShowRing = showImportanceRing;
+ mRingPaint = new Paint();
+ mRingPaint.setStyle(Paint.Style.STROKE);
+ mRingPaint.setColor(ringColor);
+ mPaddingPaint = new Paint();
+ mPaddingPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+ mPaddingPaint.setColor(Color.WHITE);
+ }
+
+ /**
+ * Show or hide the importance ring.
+ */
+ public void setImportant(boolean important) {
+ if (important != mShowRing) {
+ mShowRing = important;
+ invalidateSelf();
+ }
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mIconSize;
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mIconSize;
+ }
+
+ // Similar to badgeWithDrawable, but relying on the bounds of each underlying drawable
+ @Override
+ public void draw(Canvas canvas) {
+ final Rect bounds = getBounds();
+
+ // scale to our internal grid
+ final float scale = bounds.width() / BASE_ICON_SIZE;
+ final int ringStrokeWidth = (int) (RING_STROKE_WIDTH * scale);
+ final int headSize = (int) (HEAD_SIZE * scale);
+ final int badgePadding = (int) (BADGE_ORIGIN * scale);
+ final int badgeCenter = (int) (BADGE_CENTER * scale);
+
+ mPaddingPaint.setStrokeWidth(ringStrokeWidth);
+ final float radius = (int) (CIRCLE_RADIUS * scale); // stroke outside
+ if (mBaseIcon != null) {
+ mBaseIcon.setBounds(0,
+ 0,
+ headSize ,
+ headSize);
+ mBaseIcon.draw(canvas);
+ } else {
+ Log.w("ConversationIconFactory", "ConversationIconDrawable has null base icon");
+ }
+ if (mBadgeIcon != null) {
+ canvas.drawCircle(badgeCenter, badgeCenter, radius, mPaddingPaint);
+ mBadgeIcon.setBounds(
+ badgePadding,
+ badgePadding,
+ headSize,
+ headSize);
+ mBadgeIcon.draw(canvas);
+ } else {
+ Log.w("ConversationIconFactory", "ConversationIconDrawable has null badge icon");
+ }
+ if (mShowRing) {
+ mRingPaint.setStrokeWidth(ringStrokeWidth);
+ canvas.drawCircle(badgeCenter, badgeCenter, radius, mRingPaint);
+ }
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ // unimplemented
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ // unimplemented
+ }
+
+ @Override
+ public int getOpacity() {
+ return 0;
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
index f14def1afce5..a210e90a3cfc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
@@ -33,6 +33,7 @@ import android.util.Log;
import android.util.Slog;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -328,7 +329,6 @@ public class EnableZenModeDialog {
boolean enabled, int rowId, Uri conditionId) {
if (tag.lines == null) {
tag.lines = row.findViewById(android.R.id.content);
- tag.lines.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
}
if (tag.line1 == null) {
tag.line1 = (TextView) row.findViewById(android.R.id.text1);
@@ -358,44 +358,46 @@ public class EnableZenModeDialog {
}
});
- // minus button
- final ImageView button1 = (ImageView) row.findViewById(android.R.id.button1);
- button1.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- onClickTimeButton(row, tag, false /*down*/, rowId);
- }
- });
-
- // plus button
- final ImageView button2 = (ImageView) row.findViewById(android.R.id.button2);
- button2.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- onClickTimeButton(row, tag, true /*up*/, rowId);
- }
- });
-
final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId);
+ final ImageView minusButton = (ImageView) row.findViewById(android.R.id.button1);
+ final ImageView plusButton = (ImageView) row.findViewById(android.R.id.button2);
if (rowId == COUNTDOWN_CONDITION_INDEX && time > 0) {
- button1.setVisibility(View.VISIBLE);
- button2.setVisibility(View.VISIBLE);
+ minusButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ onClickTimeButton(row, tag, false /*down*/, rowId);
+ tag.lines.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
+ }
+ });
+
+ plusButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ onClickTimeButton(row, tag, true /*up*/, rowId);
+ tag.lines.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
+ }
+ });
if (mBucketIndex > -1) {
- button1.setEnabled(mBucketIndex > 0);
- button2.setEnabled(mBucketIndex < MINUTE_BUCKETS.length - 1);
+ minusButton.setEnabled(mBucketIndex > 0);
+ plusButton.setEnabled(mBucketIndex < MINUTE_BUCKETS.length - 1);
} else {
final long span = time - System.currentTimeMillis();
- button1.setEnabled(span > MIN_BUCKET_MINUTES * MINUTES_MS);
+ minusButton.setEnabled(span > MIN_BUCKET_MINUTES * MINUTES_MS);
final Condition maxCondition = ZenModeConfig.toTimeCondition(mContext,
MAX_BUCKET_MINUTES, ActivityManager.getCurrentUser());
- button2.setEnabled(!Objects.equals(condition.summary, maxCondition.summary));
+ plusButton.setEnabled(!Objects.equals(condition.summary, maxCondition.summary));
}
- button1.setAlpha(button1.isEnabled() ? 1f : .5f);
- button2.setAlpha(button2.isEnabled() ? 1f : .5f);
+ minusButton.setAlpha(minusButton.isEnabled() ? 1f : .5f);
+ plusButton.setAlpha(plusButton.isEnabled() ? 1f : .5f);
} else {
- button1.setVisibility(View.GONE);
- button2.setVisibility(View.GONE);
+ if (minusButton != null) {
+ ((ViewGroup) row).removeView(minusButton);
+ }
+
+ if (plusButton != null) {
+ ((ViewGroup) row).removeView(plusButton);
+ }
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java
index 66ee8021957f..87e97b17b914 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java
@@ -25,6 +25,7 @@ import android.service.notification.Condition;
import android.service.notification.ZenModeConfig;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -228,37 +229,40 @@ public class ZenDurationDialog {
}
private void updateButtons(ConditionTag tag, View row, int rowIndex) {
- // minus button
- final ImageView button1 = (ImageView) row.findViewById(android.R.id.button1);
- button1.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- onClickTimeButton(row, tag, false /*down*/, rowIndex);
- }
- });
-
- // plus button
- final ImageView button2 = (ImageView) row.findViewById(android.R.id.button2);
- button2.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- onClickTimeButton(row, tag, true /*up*/, rowIndex);
- }
- });
-
+ final ImageView minusButton = (ImageView) row.findViewById(android.R.id.button1);
+ final ImageView plusButton = (ImageView) row.findViewById(android.R.id.button2);
final long time = tag.countdownZenDuration;
if (rowIndex == COUNTDOWN_CONDITION_INDEX) {
- button1.setVisibility(View.VISIBLE);
- button2.setVisibility(View.VISIBLE);
+ minusButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ onClickTimeButton(row, tag, false /*down*/, rowIndex);
+ tag.lines.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
+ }
+ });
- button1.setEnabled(time > MIN_BUCKET_MINUTES);
- button2.setEnabled(tag.countdownZenDuration != MAX_BUCKET_MINUTES);
+ plusButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ onClickTimeButton(row, tag, true /*up*/, rowIndex);
+ tag.lines.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
+ }
+ });
+ minusButton.setVisibility(View.VISIBLE);
+ plusButton.setVisibility(View.VISIBLE);
- button1.setAlpha(button1.isEnabled() ? 1f : .5f);
- button2.setAlpha(button2.isEnabled() ? 1f : .5f);
+ minusButton.setEnabled(time > MIN_BUCKET_MINUTES);
+ plusButton.setEnabled(tag.countdownZenDuration != MAX_BUCKET_MINUTES);
+
+ minusButton.setAlpha(minusButton.isEnabled() ? 1f : .5f);
+ plusButton.setAlpha(plusButton.isEnabled() ? 1f : .5f);
} else {
- button1.setVisibility(View.GONE);
- button2.setVisibility(View.GONE);
+ if (minusButton != null) {
+ ((ViewGroup) row).removeView(minusButton);
+ }
+ if (plusButton != null) {
+ ((ViewGroup) row).removeView(plusButton);
+ }
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/ColorUtil.java b/packages/SettingsLib/src/com/android/settingslib/utils/ColorUtil.java
new file mode 100644
index 000000000000..c54b471135d1
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/ColorUtil.java
@@ -0,0 +1,35 @@
+/*
+ * 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.settingslib.utils;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+
+/** Utility class for getting color attribute **/
+public class ColorUtil {
+
+ /**
+ * Returns android:disabledAlpha value in context
+ */
+ public static float getDisabledAlpha(Context context) {
+ final TypedArray ta = context.obtainStyledAttributes(
+ new int[]{android.R.attr.disabledAlpha});
+ final float alpha = ta.getFloat(0, 0);
+ ta.recycle();
+ return alpha;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java b/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java
index 7046d234904b..0a70f72518d4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java
@@ -119,7 +119,7 @@ public class PowerUtil {
return null;
}
if (drainTimeMs <= ONE_DAY_MILLIS) {
- return context.getString(R.string.power_suggestion_extend_battery,
+ return context.getString(R.string.power_suggestion_battery_run_out,
getDateTimeStringFromMs(context, drainTimeMs));
} else {
return getMoreThanOneDayShortString(context, drainTimeMs,
diff --git a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java
index a31b71e2cd0b..15576182c53a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java
@@ -16,11 +16,14 @@
package com.android.settingslib.widget;
+import android.annotation.StringRes;
import android.content.Context;
+import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.util.AttributeSet;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.core.content.res.TypedArrayUtils;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
@@ -55,9 +58,84 @@ public class FooterPreference extends Preference {
title.setLongClickable(false);
}
+ @Override
+ public void setSummary(CharSequence summary) {
+ setTitle(summary);
+ }
+
+ @Override
+ public void setSummary(int summaryResId) {
+ setTitle(summaryResId);
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ return getTitle();
+ }
+
private void init() {
- setIcon(R.drawable.ic_info_outline_24);
- setKey(KEY_FOOTER);
+ if (getIcon() == null) {
+ setIcon(R.drawable.ic_info_outline_24);
+ }
setOrder(ORDER_FOOTER);
+ if (TextUtils.isEmpty(getKey())) {
+ setKey(KEY_FOOTER);
+ }
+ }
+
+ /**
+ * The builder is convenient to creat a dynamic FooterPreference.
+ */
+ public static class Builder {
+ private Context mContext;
+ private String mKey;
+ private CharSequence mTitle;
+
+ public Builder(@NonNull Context context) {
+ mContext = context;
+ }
+
+ /**
+ * To set the key value of the {@link FooterPreference}.
+ * @param key The key value.
+ */
+ public Builder setKey(@NonNull String key) {
+ mKey = key;
+ return this;
+ }
+
+ /**
+ * To set the title of the {@link FooterPreference}.
+ * @param title The title.
+ */
+ public Builder setTitle(CharSequence title) {
+ mTitle = title;
+ return this;
+ }
+
+ /**
+ * To set the title of the {@link FooterPreference}.
+ * @param titleResId The resource id of the title.
+ */
+ public Builder setTitle(@StringRes int titleResId) {
+ mTitle = mContext.getText(titleResId);
+ return this;
+ }
+
+ /**
+ * To generate the {@link FooterPreference}.
+ */
+ public FooterPreference build() {
+ final FooterPreference footerPreference = new FooterPreference(mContext);
+ footerPreference.setSelectable(false);
+ if (TextUtils.isEmpty(mTitle)) {
+ throw new IllegalArgumentException("Footer title cannot be empty!");
+ }
+ footerPreference.setTitle(mTitle);
+ if (!TextUtils.isEmpty(mKey)) {
+ footerPreference.setKey(mKey);
+ }
+ return footerPreference;
+ }
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixin.java b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixin.java
deleted file mode 100644
index fcf236337703..000000000000
--- a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixin.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2016 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.settingslib.widget;
-
-import android.content.Context;
-
-import androidx.preference.PreferenceFragment;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settingslib.core.lifecycle.Lifecycle;
-import com.android.settingslib.core.lifecycle.LifecycleObserver;
-import com.android.settingslib.core.lifecycle.events.SetPreferenceScreen;
-
-/**
- * Framework mixin is deprecated, use the compat version instead.
- *
- * @deprecated
- */
-@Deprecated
-public class FooterPreferenceMixin implements LifecycleObserver, SetPreferenceScreen {
-
- private final PreferenceFragment mFragment;
- private FooterPreference mFooterPreference;
-
- public FooterPreferenceMixin(PreferenceFragment fragment, Lifecycle lifecycle) {
- mFragment = fragment;
- lifecycle.addObserver(this);
- }
-
- @Override
- public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
- if (mFooterPreference != null) {
- preferenceScreen.addPreference(mFooterPreference);
- }
- }
-
- /**
- * Creates a new {@link FooterPreference}.
- */
- public FooterPreference createFooterPreference() {
- final PreferenceScreen screen = mFragment.getPreferenceScreen();
- if (mFooterPreference != null && screen != null) {
- screen.removePreference(mFooterPreference);
- }
- mFooterPreference = new FooterPreference(getPrefContext());
-
- if (screen != null) {
- screen.addPreference(mFooterPreference);
- }
- return mFooterPreference;
- }
-
- /**
- * Returns an UI context with theme properly set for new Preference objects.
- */
- private Context getPrefContext() {
- return mFragment.getPreferenceManager().getContext();
- }
-
- public boolean hasFooter() {
- return mFooterPreference != null;
- }
-}
-
diff --git a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixinCompat.java b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixinCompat.java
deleted file mode 100644
index d45e56d486ae..000000000000
--- a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixinCompat.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2018 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.settingslib.widget;
-
-import android.content.Context;
-
-import androidx.preference.PreferenceFragmentCompat;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settingslib.core.lifecycle.Lifecycle;
-import com.android.settingslib.core.lifecycle.LifecycleObserver;
-import com.android.settingslib.core.lifecycle.events.SetPreferenceScreen;
-
-public class FooterPreferenceMixinCompat implements LifecycleObserver, SetPreferenceScreen {
-
- private final PreferenceFragmentCompat mFragment;
- private FooterPreference mFooterPreference;
-
- public FooterPreferenceMixinCompat(PreferenceFragmentCompat fragment, Lifecycle lifecycle) {
- mFragment = fragment;
- lifecycle.addObserver(this);
- }
-
- @Override
- public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
- if (mFooterPreference != null) {
- preferenceScreen.addPreference(mFooterPreference);
- }
- }
-
- /**
- * Creates a new {@link FooterPreference}.
- */
- public FooterPreference createFooterPreference() {
- final PreferenceScreen screen = mFragment.getPreferenceScreen();
- if (mFooterPreference != null && screen != null) {
- screen.removePreference(mFooterPreference);
- }
- mFooterPreference = new FooterPreference(getPrefContext());
-
- if (screen != null) {
- screen.addPreference(mFooterPreference);
- }
- return mFooterPreference;
- }
-
- /**
- * Returns an UI context with theme properly set for new Preference objects.
- */
- private Context getPrefContext() {
- return mFragment.getPreferenceManager().getContext();
- }
-
- public boolean hasFooter() {
- return mFooterPreference != null;
- }
-}
-
diff --git a/packages/SettingsLib/src/com/android/settingslib/widget/UpdatableListPreferenceDialogFragment.java b/packages/SettingsLib/src/com/android/settingslib/widget/UpdatableListPreferenceDialogFragment.java
new file mode 100644
index 000000000000..bd86d673c0d1
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/widget/UpdatableListPreferenceDialogFragment.java
@@ -0,0 +1,174 @@
+/*
+ * 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.settingslib.widget;
+
+import android.content.res.TypedArray;
+import android.os.Bundle;
+import android.widget.ArrayAdapter;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.appcompat.app.AlertDialog.Builder;
+import androidx.preference.ListPreference;
+import androidx.preference.PreferenceDialogFragmentCompat;
+
+import com.android.settingslib.core.instrumentation.Instrumentable;
+
+import java.util.ArrayList;
+
+/**
+ * {@link PreferenceDialogFragmentCompat} that updates the available options
+ * when {@code onListPreferenceUpdated} is called."
+ */
+public class UpdatableListPreferenceDialogFragment extends PreferenceDialogFragmentCompat implements
+ Instrumentable {
+
+ private static final String SAVE_STATE_INDEX = "UpdatableListPreferenceDialogFragment.index";
+ private static final String SAVE_STATE_ENTRIES =
+ "UpdatableListPreferenceDialogFragment.entries";
+ private static final String SAVE_STATE_ENTRY_VALUES =
+ "UpdatableListPreferenceDialogFragment.entryValues";
+ private static final String METRICS_CATEGORY_KEY = "metrics_category_key";
+ private ArrayAdapter mAdapter;
+ private int mClickedDialogEntryIndex;
+ private ArrayList<CharSequence> mEntries;
+ private CharSequence[] mEntryValues;
+ private int mMetricsCategory = METRICS_CATEGORY_UNKNOWN;
+
+ /**
+ * Creates a new instance of {@link UpdatableListPreferenceDialogFragment}.
+ */
+ public static UpdatableListPreferenceDialogFragment newInstance(
+ String key, int metricsCategory) {
+ UpdatableListPreferenceDialogFragment fragment =
+ new UpdatableListPreferenceDialogFragment();
+ Bundle args = new Bundle(1);
+ args.putString(ARG_KEY, key);
+ args.putInt(METRICS_CATEGORY_KEY, metricsCategory);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final Bundle bundle = getArguments();
+ mMetricsCategory =
+ bundle.getInt(METRICS_CATEGORY_KEY, METRICS_CATEGORY_UNKNOWN);
+ if (savedInstanceState == null) {
+ mEntries = new ArrayList<>();
+ setPreferenceData(getListPreference());
+ } else {
+ mClickedDialogEntryIndex = savedInstanceState.getInt(SAVE_STATE_INDEX, 0);
+ mEntries = savedInstanceState.getCharSequenceArrayList(SAVE_STATE_ENTRIES);
+ mEntryValues =
+ savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRY_VALUES);
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(SAVE_STATE_INDEX, mClickedDialogEntryIndex);
+ outState.putCharSequenceArrayList(SAVE_STATE_ENTRIES, mEntries);
+ outState.putCharSequenceArray(SAVE_STATE_ENTRY_VALUES, mEntryValues);
+ }
+
+ @Override
+ public void onDialogClosed(boolean positiveResult) {
+ if (positiveResult && mClickedDialogEntryIndex >= 0) {
+ final ListPreference preference = getListPreference();
+ final String value = mEntryValues[mClickedDialogEntryIndex].toString();
+ if (preference.callChangeListener(value)) {
+ preference.setValue(value);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void setAdapter(ArrayAdapter adapter) {
+ mAdapter = adapter;
+ }
+
+ @VisibleForTesting
+ void setEntries(ArrayList<CharSequence> entries) {
+ mEntries = entries;
+ }
+
+ @VisibleForTesting
+ ArrayAdapter getAdapter() {
+ return mAdapter;
+ }
+
+ @VisibleForTesting
+ void setMetricsCategory(Bundle bundle) {
+ mMetricsCategory =
+ bundle.getInt(METRICS_CATEGORY_KEY, METRICS_CATEGORY_UNKNOWN);
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(Builder builder) {
+ super.onPrepareDialogBuilder(builder);
+ final TypedArray a = getContext().obtainStyledAttributes(
+ null,
+ com.android.internal.R.styleable.AlertDialog,
+ com.android.internal.R.attr.alertDialogStyle, 0);
+
+ mAdapter = new ArrayAdapter<>(
+ getContext(),
+ a.getResourceId(
+ com.android.internal.R.styleable.AlertDialog_singleChoiceItemLayout,
+ com.android.internal.R.layout.select_dialog_singlechoice),
+ mEntries);
+
+ builder.setSingleChoiceItems(mAdapter, mClickedDialogEntryIndex,
+ (dialog, which) -> {
+ mClickedDialogEntryIndex = which;
+ onClick(dialog, -1);
+ dialog.dismiss();
+ });
+ builder.setPositiveButton(null, null);
+ a.recycle();
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return mMetricsCategory;
+ }
+
+ @VisibleForTesting
+ ListPreference getListPreference() {
+ return (ListPreference) getPreference();
+ }
+
+ private void setPreferenceData(ListPreference preference) {
+ mEntries.clear();
+ mClickedDialogEntryIndex = preference.findIndexOfValue(preference.getValue());
+ for (CharSequence entry : preference.getEntries()) {
+ mEntries.add(entry);
+ }
+ mEntryValues = preference.getEntryValues();
+ }
+
+ /**
+ * Update new data set for list preference.
+ */
+ public void onListPreferenceUpdated(ListPreference preference) {
+ if (mAdapter != null) {
+ setPreferenceData(preference);
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 09c8f7398199..8968340b65f4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -16,6 +16,9 @@
package com.android.settingslib.wifi;
+import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED;
+import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED;
+
import android.annotation.IntDef;
import android.annotation.MainThread;
import android.annotation.Nullable;
@@ -33,11 +36,9 @@ import android.net.NetworkKey;
import android.net.NetworkScoreManager;
import android.net.NetworkScorerAppData;
import android.net.ScoredNetwork;
-import android.net.wifi.IWifiManager;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.KeyMgmt;
-import android.net.wifi.WifiEnterpriseConfig;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiNetworkScoreCache;
@@ -47,7 +48,6 @@ import android.net.wifi.hotspot2.ProvisioningCallback;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
@@ -83,7 +83,16 @@ import java.util.concurrent.atomic.AtomicInteger;
* <p>An AccessPoint, which would be more fittingly named "WifiNetwork", is an aggregation of
* {@link ScanResult ScanResults} along with pertinent metadata (e.g. current connection info,
* network scores) required to successfully render the network to the user.
+ *
+ * @deprecated WifiTracker/AccessPoint is no longer supported, and will be removed in a future
+ * release. Clients that need a dynamic list of available wifi networks should migrate to one of the
+ * newer tracker classes,
+ * {@link com.android.wifitrackerlib.WifiPickerTracker},
+ * {@link com.android.wifitrackerlib.SavedNetworkTracker},
+ * {@link com.android.wifitrackerlib.NetworkDetailsTracker},
+ * in conjunction with {@link com.android.wifitrackerlib.WifiEntry} to represent each wifi network.
*/
+@Deprecated
public class AccessPoint implements Comparable<AccessPoint> {
static final String TAG = "SettingsLib.AccessPoint";
@@ -143,6 +152,16 @@ public class AccessPoint implements Comparable<AccessPoint> {
int VERY_FAST = 30;
}
+ @IntDef({PasspointConfigurationVersion.INVALID,
+ PasspointConfigurationVersion.NO_OSU_PROVISIONED,
+ PasspointConfigurationVersion.OSU_PROVISIONED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PasspointConfigurationVersion {
+ int INVALID = 0;
+ int NO_OSU_PROVISIONED = 1; // R1.
+ int OSU_PROVISIONED = 2; // R2 or R3.
+ }
+
/** The underlying set of scan results comprising this AccessPoint. */
@GuardedBy("mLock")
private final ArraySet<ScanResult> mScanResults = new ArraySet<>();
@@ -171,12 +190,13 @@ public class AccessPoint implements Comparable<AccessPoint> {
static final String KEY_SCANRESULTS = "key_scanresults";
static final String KEY_SCOREDNETWORKCACHE = "key_scorednetworkcache";
static final String KEY_CONFIG = "key_config";
+ static final String KEY_PASSPOINT_UNIQUE_ID = "key_passpoint_unique_id";
static final String KEY_FQDN = "key_fqdn";
static final String KEY_PROVIDER_FRIENDLY_NAME = "key_provider_friendly_name";
- static final String KEY_IS_CARRIER_AP = "key_is_carrier_ap";
- static final String KEY_CARRIER_AP_EAP_TYPE = "key_carrier_ap_eap_type";
- static final String KEY_CARRIER_NAME = "key_carrier_name";
static final String KEY_EAPTYPE = "eap_psktype";
+ static final String KEY_SUBSCRIPTION_EXPIRATION_TIME_IN_MILLIS =
+ "key_subscription_expiration_time_in_millis";
+ static final String KEY_PASSPOINT_CONFIGURATION_VERSION = "key_passpoint_configuration_version";
static final String KEY_IS_PSK_SAE_TRANSITION_MODE = "key_is_psk_sae_transition_mode";
static final String KEY_IS_OWE_TRANSITION_MODE = "key_is_owe_transition_mode";
static final AtomicInteger sLastId = new AtomicInteger(0);
@@ -204,17 +224,10 @@ public class AccessPoint implements Comparable<AccessPoint> {
private static final int EAP_WPA = 1; // WPA-EAP
private static final int EAP_WPA2_WPA3 = 2; // RSN-EAP
- /**
- * The number of distinct wifi levels.
- *
- * <p>Must keep in sync with {@link R.array.wifi_signal} and {@link WifiManager#RSSI_LEVELS}.
- */
- public static final int SIGNAL_LEVELS = 5;
-
public static final int UNREACHABLE_RSSI = Integer.MIN_VALUE;
public static final String KEY_PREFIX_AP = "AP:";
- public static final String KEY_PREFIX_FQDN = "FQDN:";
+ public static final String KEY_PREFIX_PASSPOINT_UNIQUE_ID = "PASSPOINT:";
public static final String KEY_PREFIX_OSU = "OSU:";
private final Context mContext;
@@ -247,11 +260,13 @@ public class AccessPoint implements Comparable<AccessPoint> {
* Information associated with the {@link PasspointConfiguration}. Only maintaining
* the relevant info to preserve spaces.
*/
+ private String mPasspointUniqueId;
private String mFqdn;
private String mProviderFriendlyName;
private boolean mIsRoaming = false;
-
- private boolean mIsCarrierAp = false;
+ private long mSubscriptionExpirationTimeInMillis;
+ @PasspointConfigurationVersion private int mPasspointConfigurationVersion =
+ PasspointConfigurationVersion.INVALID;
private OsuProvider mOsuProvider;
@@ -262,12 +277,6 @@ public class AccessPoint implements Comparable<AccessPoint> {
private boolean mIsPskSaeTransitionMode = false;
private boolean mIsOweTransitionMode = false;
- /**
- * The EAP type {@link WifiEnterpriseConfig.Eap} associated with this AP if it is a carrier AP.
- */
- private int mCarrierApEapType = WifiEnterpriseConfig.Eap.NONE;
- private String mCarrierName = null;
-
public AccessPoint(Context context, Bundle savedState) {
mContext = context;
@@ -310,20 +319,21 @@ public class AccessPoint implements Comparable<AccessPoint> {
mScoredNetworkCache.put(timedScore.getScore().networkKey.wifiKey.bssid, timedScore);
}
}
+ if (savedState.containsKey(KEY_PASSPOINT_UNIQUE_ID)) {
+ mPasspointUniqueId = savedState.getString(KEY_PASSPOINT_UNIQUE_ID);
+ }
if (savedState.containsKey(KEY_FQDN)) {
mFqdn = savedState.getString(KEY_FQDN);
}
if (savedState.containsKey(KEY_PROVIDER_FRIENDLY_NAME)) {
mProviderFriendlyName = savedState.getString(KEY_PROVIDER_FRIENDLY_NAME);
}
- if (savedState.containsKey(KEY_IS_CARRIER_AP)) {
- mIsCarrierAp = savedState.getBoolean(KEY_IS_CARRIER_AP);
- }
- if (savedState.containsKey(KEY_CARRIER_AP_EAP_TYPE)) {
- mCarrierApEapType = savedState.getInt(KEY_CARRIER_AP_EAP_TYPE);
+ if (savedState.containsKey(KEY_SUBSCRIPTION_EXPIRATION_TIME_IN_MILLIS)) {
+ mSubscriptionExpirationTimeInMillis =
+ savedState.getLong(KEY_SUBSCRIPTION_EXPIRATION_TIME_IN_MILLIS);
}
- if (savedState.containsKey(KEY_CARRIER_NAME)) {
- mCarrierName = savedState.getString(KEY_CARRIER_NAME);
+ if (savedState.containsKey(KEY_PASSPOINT_CONFIGURATION_VERSION)) {
+ mPasspointConfigurationVersion = savedState.getInt(KEY_PASSPOINT_CONFIGURATION_VERSION);
}
if (savedState.containsKey(KEY_IS_PSK_SAE_TRANSITION_MODE)) {
mIsPskSaeTransitionMode = savedState.getBoolean(KEY_IS_PSK_SAE_TRANSITION_MODE);
@@ -355,8 +365,15 @@ public class AccessPoint implements Comparable<AccessPoint> {
*/
public AccessPoint(Context context, PasspointConfiguration config) {
mContext = context;
+ mPasspointUniqueId = config.getUniqueId();
mFqdn = config.getHomeSp().getFqdn();
mProviderFriendlyName = config.getHomeSp().getFriendlyName();
+ mSubscriptionExpirationTimeInMillis = config.getSubscriptionExpirationTimeMillis();
+ if (config.isOsuProvisioned()) {
+ mPasspointConfigurationVersion = PasspointConfigurationVersion.OSU_PROVISIONED;
+ } else {
+ mPasspointConfigurationVersion = PasspointConfigurationVersion.NO_OSU_PROVISIONED;
+ }
updateKey();
}
@@ -369,6 +386,7 @@ public class AccessPoint implements Comparable<AccessPoint> {
mContext = context;
networkId = config.networkId;
mConfig = config;
+ mPasspointUniqueId = config.getKey();
mFqdn = config.FQDN;
setScanResultsPasspoint(homeScans, roamingScans);
updateKey();
@@ -405,7 +423,7 @@ public class AccessPoint implements Comparable<AccessPoint> {
if (isPasspoint()) {
mKey = getKey(mConfig);
} else if (isPasspointConfig()) {
- mKey = getKey(mFqdn);
+ mKey = getKey(mPasspointUniqueId);
} else if (isOsuProvider()) {
mKey = getKey(mOsuProvider);
} else { // Non-Passpoint AP
@@ -447,9 +465,10 @@ public class AccessPoint implements Comparable<AccessPoint> {
return other.getSpeed() - getSpeed();
}
+ WifiManager wifiManager = getWifiManager();
// Sort by signal strength, bucketed by level
- int difference = WifiManager.calculateSignalLevel(other.mRssi, SIGNAL_LEVELS)
- - WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS);
+ int difference = wifiManager.calculateSignalLevel(other.mRssi)
+ - wifiManager.calculateSignalLevel(mRssi);
if (difference != 0) {
return difference;
}
@@ -654,7 +673,7 @@ public class AccessPoint implements Comparable<AccessPoint> {
}
}
}
- return oldMetering == mIsScoredNetworkMetered;
+ return oldMetering != mIsScoredNetworkMetered;
}
/**
@@ -674,19 +693,19 @@ public class AccessPoint implements Comparable<AccessPoint> {
*/
public static String getKey(WifiConfiguration config) {
if (config.isPasspoint()) {
- return getKey(config.FQDN);
+ return getKey(config.getKey());
} else {
return getKey(removeDoubleQuotes(config.SSID), config.BSSID, getSecurity(config));
}
}
/**
- * Returns the AccessPoint key corresponding to a Passpoint network by its FQDN.
+ * Returns the AccessPoint key corresponding to a Passpoint network by its unique identifier.
*/
- public static String getKey(String fqdn) {
+ public static String getKey(String passpointUniqueId) {
return new StringBuilder()
- .append(KEY_PREFIX_FQDN)
- .append(fqdn).toString();
+ .append(KEY_PREFIX_PASSPOINT_UNIQUE_ID)
+ .append(passpointUniqueId).toString();
}
/**
@@ -763,7 +782,7 @@ public class AccessPoint implements Comparable<AccessPoint> {
public boolean matches(WifiConfiguration config) {
if (config.isPasspoint()) {
- return (isPasspoint() && config.FQDN.equals(mConfig.FQDN));
+ return (isPasspoint() && config.getKey().equals(mConfig.getKey()));
}
if (!ssid.equals(removeDoubleQuotes(config.SSID))
@@ -863,13 +882,14 @@ public class AccessPoint implements Comparable<AccessPoint> {
}
/**
- * Returns the number of levels to show for a Wifi icon, from 0 to {@link #SIGNAL_LEVELS}-1.
+ * Returns the number of levels to show for a Wifi icon, from 0 to
+ * {@link WifiManager#getMaxSignalLevel()}.
*
- * <p>Use {@#isReachable()} to determine if an AccessPoint is in range, as this method will
+ * <p>Use {@link #isReachable()} to determine if an AccessPoint is in range, as this method will
* always return at least 0.
*/
public int getLevel() {
- return WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS);
+ return getWifiManager().calculateSignalLevel(mRssi);
}
public int getRssi() {
@@ -939,10 +959,6 @@ public class AccessPoint implements Comparable<AccessPoint> {
mIsPskSaeTransitionMode = AccessPoint.isPskSaeTransitionMode(bestResult);
mIsOweTransitionMode = AccessPoint.isOweTransitionMode(bestResult);
-
- mIsCarrierAp = bestResult.isCarrierAp;
- mCarrierApEapType = bestResult.carrierApEapType;
- mCarrierName = bestResult.carrierName;
}
// Update the config SSID of a Passpoint network to that of the best RSSI
if (isPasspoint()) {
@@ -977,6 +993,10 @@ public class AccessPoint implements Comparable<AccessPoint> {
return concise ? context.getString(R.string.wifi_security_short_psk_sae) :
context.getString(R.string.wifi_security_psk_sae);
}
+ if (mIsOweTransitionMode) {
+ return concise ? context.getString(R.string.wifi_security_short_none_owe) :
+ context.getString(R.string.wifi_security_none_owe);
+ }
switch(security) {
case SECURITY_EAP:
@@ -1048,7 +1068,7 @@ public class AccessPoint implements Comparable<AccessPoint> {
public String getConfigName() {
if (mConfig != null && mConfig.isPasspoint()) {
return mConfig.providerFriendlyName;
- } else if (mFqdn != null) {
+ } else if (mPasspointUniqueId != null) {
return mProviderFriendlyName;
} else {
return ssid;
@@ -1063,18 +1083,6 @@ public class AccessPoint implements Comparable<AccessPoint> {
return null;
}
- public boolean isCarrierAp() {
- return mIsCarrierAp;
- }
-
- public int getCarrierApEapType() {
- return mCarrierApEapType;
- }
-
- public String getCarrierName() {
- return mCarrierName;
- }
-
public String getSavedNetworkSummary() {
WifiConfiguration config = mConfig;
if (config != null) {
@@ -1098,6 +1106,10 @@ public class AccessPoint implements Comparable<AccessPoint> {
return mContext.getString(R.string.saved_network, appInfo.loadLabel(pm));
}
}
+
+ if (isPasspointConfigurationR1() && isExpired()) {
+ return mContext.getString(R.string.wifi_passpoint_expired);
+ }
return "";
}
@@ -1128,6 +1140,10 @@ public class AccessPoint implements Comparable<AccessPoint> {
* Returns the summary for the AccessPoint.
*/
public String getSettingsSummary(boolean convertSavedAsDisconnected) {
+ if (isPasspointConfigurationR1() && isExpired()) {
+ return mContext.getString(R.string.wifi_passpoint_expired);
+ }
+
// Update to new summary
StringBuilder summary = new StringBuilder();
@@ -1142,22 +1158,20 @@ public class AccessPoint implements Comparable<AccessPoint> {
summary.append(mContext.getString(R.string.tap_to_sign_up));
}
} else if (isActive()) {
- if (getDetailedState() == DetailedState.CONNECTED && mIsCarrierAp) {
- // This is the active connection on a carrier AP
- summary.append(String.format(mContext.getString(R.string.connected_via_carrier),
- mCarrierName));
- } else {
- summary.append(getSummary(mContext, /* ssid */ null, getDetailedState(),
- mInfo != null && mInfo.isEphemeral(),
- mInfo != null ? mInfo.getNetworkSuggestionOrSpecifierPackageName() : null));
- }
+ summary.append(getSummary(mContext, /* ssid */ null, getDetailedState(),
+ mInfo != null && mInfo.isEphemeral(),
+ mInfo != null ? mInfo.getRequestingPackageName() : null));
} else { // not active
if (mConfig != null && mConfig.hasNoInternetAccess()) {
- int messageID = mConfig.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()
+ int messageID =
+ mConfig.getNetworkSelectionStatus().getNetworkSelectionStatus()
+ == NETWORK_SELECTION_PERMANENTLY_DISABLED
? R.string.wifi_no_internet_no_reconnect
: R.string.wifi_no_internet;
summary.append(mContext.getString(messageID));
- } else if (mConfig != null && !mConfig.getNetworkSelectionStatus().isNetworkEnabled()) {
+ } else if (mConfig != null
+ && (mConfig.getNetworkSelectionStatus().getNetworkSelectionStatus()
+ != NETWORK_SELECTION_ENABLED)) {
WifiConfiguration.NetworkSelectionStatus networkStatus =
mConfig.getNetworkSelectionStatus();
switch (networkStatus.getNetworkSelectionDisableReason()) {
@@ -1168,26 +1182,19 @@ public class AccessPoint implements Comparable<AccessPoint> {
summary.append(mContext.getString(R.string.wifi_check_password_try_again));
break;
case WifiConfiguration.NetworkSelectionStatus.DISABLED_DHCP_FAILURE:
- case WifiConfiguration.NetworkSelectionStatus.DISABLED_DNS_FAILURE:
summary.append(mContext.getString(R.string.wifi_disabled_network_failure));
break;
case WifiConfiguration.NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION:
summary.append(mContext.getString(R.string.wifi_disabled_generic));
break;
}
- } else if (mConfig != null && mConfig.getNetworkSelectionStatus().isNotRecommended()) {
- summary.append(mContext.getString(
- R.string.wifi_disabled_by_recommendation_provider));
- } else if (mIsCarrierAp) {
- summary.append(String.format(mContext.getString(
- R.string.available_via_carrier), mCarrierName));
} else if (!isReachable()) { // Wifi out of range
summary.append(mContext.getString(R.string.wifi_not_in_range));
} else { // In range, not disabled.
if (mConfig != null) { // Is saved network
// Last attempt to connect to this failed. Show reason why
- switch (mConfig.recentFailure.getAssociationStatus()) {
- case WifiConfiguration.RecentFailure.STATUS_AP_UNABLE_TO_HANDLE_NEW_STA:
+ switch (mConfig.getRecentFailureReason()) {
+ case WifiConfiguration.RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA:
summary.append(mContext.getString(
R.string.wifi_ap_unable_to_handle_new_sta));
break;
@@ -1263,7 +1270,7 @@ public class AccessPoint implements Comparable<AccessPoint> {
* Return true if this AccessPoint represents a Passpoint provider configuration.
*/
public boolean isPasspointConfig() {
- return mFqdn != null && mConfig == null;
+ return mPasspointUniqueId != null && mConfig == null;
}
/**
@@ -1274,6 +1281,30 @@ public class AccessPoint implements Comparable<AccessPoint> {
}
/**
+ * Return true if this AccessPoint is expired.
+ */
+ public boolean isExpired() {
+ if (mSubscriptionExpirationTimeInMillis <= 0) {
+ // Expiration time not specified.
+ return false;
+ } else {
+ return System.currentTimeMillis() >= mSubscriptionExpirationTimeInMillis;
+ }
+ }
+
+ public boolean isPasspointConfigurationR1() {
+ return mPasspointConfigurationVersion == PasspointConfigurationVersion.NO_OSU_PROVISIONED;
+ }
+
+ /**
+ * Return true if {@link PasspointConfiguration#isOsuProvisioned} is true, this may refer to R2
+ * or R3.
+ */
+ public boolean isPasspointConfigurationOsuProvisioned() {
+ return mPasspointConfigurationVersion == PasspointConfigurationVersion.OSU_PROVISIONED;
+ }
+
+ /**
* Starts the OSU Provisioning flow.
*/
public void startOsuProvisioning(@Nullable WifiManager.ActionListener connectListener) {
@@ -1295,8 +1326,12 @@ public class AccessPoint implements Comparable<AccessPoint> {
if (info.isOsuAp() || mOsuStatus != null) {
return (info.isOsuAp() && mOsuStatus != null);
} else if (info.isPasspointAp() || isPasspoint()) {
+ // TODO: Use TextUtils.equals(info.getPasspointUniqueId(), mConfig.getKey()) when API
+ // is available
return (info.isPasspointAp() && isPasspoint()
- && TextUtils.equals(info.getPasspointFqdn(), mConfig.FQDN));
+ && TextUtils.equals(info.getPasspointFqdn(), mConfig.FQDN)
+ && TextUtils.equals(info.getPasspointProviderFriendlyName(),
+ mConfig.providerFriendlyName));
}
if (networkId != WifiConfiguration.INVALID_NETWORK_ID) {
@@ -1328,7 +1363,7 @@ public class AccessPoint implements Comparable<AccessPoint> {
* Can only be called for unsecured networks.
*/
public void generateOpenNetworkConfig() {
- if ((security != SECURITY_NONE) && (security != SECURITY_OWE)) {
+ if (!isOpenNetwork()) {
throw new IllegalStateException();
}
if (mConfig != null)
@@ -1336,11 +1371,11 @@ public class AccessPoint implements Comparable<AccessPoint> {
mConfig = new WifiConfiguration();
mConfig.SSID = AccessPoint.convertToQuotedString(ssid);
- if (security == SECURITY_NONE || !getWifiManager().isEasyConnectSupported()) {
+ if (security == SECURITY_NONE) {
mConfig.allowedKeyManagement.set(KeyMgmt.NONE);
} else {
mConfig.allowedKeyManagement.set(KeyMgmt.OWE);
- mConfig.requirePMF = true;
+ mConfig.requirePmf = true;
}
}
@@ -1362,15 +1397,18 @@ public class AccessPoint implements Comparable<AccessPoint> {
if (mNetworkInfo != null) {
savedState.putParcelable(KEY_NETWORKINFO, mNetworkInfo);
}
+ if (mPasspointUniqueId != null) {
+ savedState.putString(KEY_PASSPOINT_UNIQUE_ID, mPasspointUniqueId);
+ }
if (mFqdn != null) {
savedState.putString(KEY_FQDN, mFqdn);
}
if (mProviderFriendlyName != null) {
savedState.putString(KEY_PROVIDER_FRIENDLY_NAME, mProviderFriendlyName);
}
- savedState.putBoolean(KEY_IS_CARRIER_AP, mIsCarrierAp);
- savedState.putInt(KEY_CARRIER_AP_EAP_TYPE, mCarrierApEapType);
- savedState.putString(KEY_CARRIER_NAME, mCarrierName);
+ savedState.putLong(KEY_SUBSCRIPTION_EXPIRATION_TIME_IN_MILLIS,
+ mSubscriptionExpirationTimeInMillis);
+ savedState.putInt(KEY_PASSPOINT_CONFIGURATION_VERSION, mPasspointConfigurationVersion);
savedState.putBoolean(KEY_IS_PSK_SAE_TRANSITION_MODE, mIsPskSaeTransitionMode);
savedState.putBoolean(KEY_IS_OWE_TRANSITION_MODE, mIsOweTransitionMode);
}
@@ -1629,13 +1667,8 @@ public class AccessPoint implements Comparable<AccessPoint> {
final ConnectivityManager cm = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (state == DetailedState.CONNECTED) {
- IWifiManager wifiManager = IWifiManager.Stub.asInterface(
- ServiceManager.getService(Context.WIFI_SERVICE));
- NetworkCapabilities nc = null;
-
- try {
- nc = cm.getNetworkCapabilities(wifiManager.getCurrentNetwork());
- } catch (RemoteException e) {}
+ WifiManager wifiManager = context.getSystemService(WifiManager.class);
+ NetworkCapabilities nc = cm.getNetworkCapabilities(wifiManager.getCurrentNetwork());
if (nc != null) {
if (nc.hasCapability(nc.NET_CAPABILITY_CAPTIVE_PORTAL)) {
@@ -1758,7 +1791,10 @@ public class AccessPoint implements Comparable<AccessPoint> {
if (config.allowedKeyManagement.get(KeyMgmt.OWE)) {
return SECURITY_OWE;
}
- return (config.wepKeys[0] != null) ? SECURITY_WEP : SECURITY_NONE;
+ return (config.wepTxKeyIndex >= 0
+ && config.wepTxKeyIndex < config.wepKeys.length
+ && config.wepKeys[config.wepTxKeyIndex] != null)
+ ? SECURITY_WEP : SECURITY_NONE;
}
public static String securityToString(int security, int pskType) {
@@ -1805,6 +1841,13 @@ public class AccessPoint implements Comparable<AccessPoint> {
}
/**
+ * Return true if this is an open network AccessPoint.
+ */
+ public boolean isOpenNetwork() {
+ return security == SECURITY_NONE || security == SECURITY_OWE;
+ }
+
+ /**
* Callbacks relaying changes to the AccessPoint representation.
*
* <p>All methods are invoked on the Main Thread.
@@ -1929,11 +1972,11 @@ public class AccessPoint implements Comparable<AccessPoint> {
return;
}
- String fqdn = passpointConfig.getHomeSp().getFqdn();
+ String uniqueId = passpointConfig.getUniqueId();
for (Pair<WifiConfiguration, Map<Integer, List<ScanResult>>> pairing :
wifiManager.getAllMatchingWifiConfigs(wifiManager.getScanResults())) {
WifiConfiguration config = pairing.first;
- if (TextUtils.equals(config.FQDN, fqdn)) {
+ if (TextUtils.equals(config.getKey(), uniqueId)) {
List<ScanResult> homeScans =
pairing.second.get(WifiManager.PASSPOINT_HOME_NETWORK);
List<ScanResult> roamingScans =
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/LongPressWifiEntryPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/LongPressWifiEntryPreference.java
new file mode 100644
index 000000000000..503d60c87bb9
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/LongPressWifiEntryPreference.java
@@ -0,0 +1,46 @@
+/*
+ * 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.settingslib.wifi;
+
+import android.content.Context;
+
+import androidx.fragment.app.Fragment;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.wifitrackerlib.WifiEntry;
+
+/**
+ * WifiEntryPreference that can be long pressed.
+ */
+public class LongPressWifiEntryPreference extends WifiEntryPreference {
+
+ private final Fragment mFragment;
+
+ public LongPressWifiEntryPreference(Context context, WifiEntry wifiEntry, Fragment fragment) {
+ super(context, wifiEntry);
+ mFragment = fragment;
+ }
+
+ @Override
+ public void onBindViewHolder(final PreferenceViewHolder view) {
+ super.onBindViewHolder(view);
+ if (mFragment != null) {
+ view.itemView.setOnCreateContextMenuListener(mFragment);
+ view.itemView.setTag(this);
+ view.itemView.setLongClickable(true);
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
index 17a73acb9bda..2fb2481ac117 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
@@ -22,6 +22,7 @@ import android.net.NetworkInfo;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Parcelable;
@@ -56,8 +57,6 @@ public class TestAccessPointBuilder {
private int mSecurity = AccessPoint.SECURITY_NONE;
private WifiConfiguration mWifiConfig;
private WifiInfo mWifiInfo;
- private boolean mIsCarrierAp = false;
- private String mCarrierName = null;
Context mContext;
private ArrayList<ScanResult> mScanResults;
@@ -85,7 +84,7 @@ public class TestAccessPointBuilder {
bundle.putParcelable(AccessPoint.KEY_NETWORKINFO, mNetworkInfo);
bundle.putParcelable(AccessPoint.KEY_WIFIINFO, mWifiInfo);
if (mFqdn != null) {
- bundle.putString(AccessPoint.KEY_FQDN, mFqdn);
+ bundle.putString(AccessPoint.KEY_PASSPOINT_UNIQUE_ID, mFqdn);
}
if (mProviderFriendlyName != null) {
bundle.putString(AccessPoint.KEY_PROVIDER_FRIENDLY_NAME, mProviderFriendlyName);
@@ -99,10 +98,6 @@ public class TestAccessPointBuilder {
}
bundle.putInt(AccessPoint.KEY_SECURITY, mSecurity);
bundle.putInt(AccessPoint.KEY_SPEED, mSpeed);
- bundle.putBoolean(AccessPoint.KEY_IS_CARRIER_AP, mIsCarrierAp);
- if (mCarrierName != null) {
- bundle.putString(AccessPoint.KEY_CARRIER_NAME, mCarrierName);
- }
AccessPoint ap = new AccessPoint(mContext, bundle);
ap.setRssi(mRssi);
@@ -132,13 +127,15 @@ public class TestAccessPointBuilder {
@Keep
public TestAccessPointBuilder setLevel(int level) {
// Reversal of WifiManager.calculateSignalLevels
+ WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
+ int maxSignalLevel = wifiManager.getMaxSignalLevel();
if (level == 0) {
mRssi = MIN_RSSI;
- } else if (level >= AccessPoint.SIGNAL_LEVELS) {
+ } else if (level > maxSignalLevel) {
mRssi = MAX_RSSI;
} else {
float inputRange = MAX_RSSI - MIN_RSSI;
- float outputRange = AccessPoint.SIGNAL_LEVELS - 1;
+ float outputRange = maxSignalLevel;
mRssi = (int) (level * inputRange / outputRange + MIN_RSSI);
}
return this;
@@ -241,16 +238,6 @@ public class TestAccessPointBuilder {
return this;
}
- public TestAccessPointBuilder setIsCarrierAp(boolean isCarrierAp) {
- mIsCarrierAp = isCarrierAp;
- return this;
- }
-
- public TestAccessPointBuilder setCarrierName(String carrierName) {
- mCarrierName = carrierName;
- return this;
- }
-
public TestAccessPointBuilder setScoredNetworkCache(
ArrayList<TimestampedScoredNetwork> scoredNetworkCache) {
mScoredNetworkCache = scoredNetworkCache;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
new file mode 100644
index 000000000000..a53bc9f966d2
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
@@ -0,0 +1,307 @@
+/*
+ * 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.settingslib.wifi;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.StateListDrawable;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settingslib.R;
+import com.android.settingslib.Utils;
+import com.android.wifitrackerlib.WifiEntry;
+
+/**
+ * Preference to display a WifiEntry in a wifi picker.
+ */
+public class WifiEntryPreference extends Preference implements WifiEntry.WifiEntryCallback,
+ View.OnClickListener {
+
+ private static final int[] STATE_SECURED = {
+ R.attr.state_encrypted
+ };
+
+ private static final int[] FRICTION_ATTRS = {
+ R.attr.wifi_friction
+ };
+
+ // These values must be kept within [WifiEntry.WIFI_LEVEL_MIN, WifiEntry.WIFI_LEVEL_MAX]
+ private static final int[] WIFI_CONNECTION_STRENGTH = {
+ R.string.accessibility_no_wifi,
+ R.string.accessibility_wifi_one_bar,
+ R.string.accessibility_wifi_two_bars,
+ R.string.accessibility_wifi_three_bars,
+ R.string.accessibility_wifi_signal_full
+ };
+
+ // StateListDrawable to display secured lock / metered "$" icon
+ @Nullable private final StateListDrawable mFrictionSld;
+ private final IconInjector mIconInjector;
+ private WifiEntry mWifiEntry;
+ private int mLevel = -1;
+ private CharSequence mContentDescription;
+ private OnButtonClickListener mOnButtonClickListener;
+
+ public WifiEntryPreference(@NonNull Context context, @NonNull WifiEntry wifiEntry) {
+ this(context, wifiEntry, new IconInjector(context));
+ }
+
+ @VisibleForTesting
+ WifiEntryPreference(@NonNull Context context, @NonNull WifiEntry wifiEntry,
+ @NonNull IconInjector iconInjector) {
+ super(context);
+
+ setLayoutResource(R.layout.preference_access_point);
+ setWidgetLayoutResource(R.layout.access_point_friction_widget);
+ mFrictionSld = getFrictionStateListDrawable();
+ mWifiEntry = wifiEntry;
+ mWifiEntry.setListener(this);
+ mIconInjector = iconInjector;
+ refresh();
+ }
+
+ public WifiEntry getWifiEntry() {
+ return mWifiEntry;
+ }
+
+ @Override
+ public void onBindViewHolder(final PreferenceViewHolder view) {
+ super.onBindViewHolder(view);
+ final Drawable drawable = getIcon();
+ if (drawable != null) {
+ drawable.setLevel(mLevel);
+ }
+
+ view.itemView.setContentDescription(mContentDescription);
+
+ // Turn off divider
+ view.findViewById(R.id.two_target_divider).setVisibility(View.INVISIBLE);
+
+ // Enable the icon button when the help string in this WifiEntry is not null.
+ final ImageButton imageButton = (ImageButton) view.findViewById(R.id.icon_button);
+ final ImageView frictionImageView = (ImageView) view.findViewById(
+ R.id.friction_icon);
+ if (mWifiEntry.getHelpUriString() != null
+ && mWifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_DISCONNECTED) {
+ final Drawable drawablehelp = getDrawable(R.drawable.ic_help);
+ drawablehelp.setTintList(
+ Utils.getColorAttr(getContext(), android.R.attr.colorControlNormal));
+ ((ImageView) imageButton).setImageDrawable(drawablehelp);
+ imageButton.setVisibility(View.VISIBLE);
+ imageButton.setOnClickListener(this);
+ imageButton.setContentDescription(
+ getContext().getText(R.string.help_label));
+
+ if (frictionImageView != null) {
+ frictionImageView.setVisibility(View.GONE);
+ }
+ } else {
+ imageButton.setVisibility(View.GONE);
+
+ if (frictionImageView != null) {
+ frictionImageView.setVisibility(View.VISIBLE);
+ bindFrictionImage(frictionImageView);
+ }
+ }
+ }
+
+ /**
+ * Updates the title and summary; may indirectly call notifyChanged().
+ */
+ public void refresh() {
+ setTitle(mWifiEntry.getTitle());
+ final int level = mWifiEntry.getLevel();
+ if (level != mLevel) {
+ mLevel = level;
+ updateIcon(mLevel);
+ notifyChanged();
+ }
+
+ setSummary(mWifiEntry.getSummary(false /* concise */));
+ mContentDescription = buildContentDescription();
+ }
+
+ /**
+ * Indicates the state of the WifiEntry has changed and clients may retrieve updates through
+ * the WifiEntry getter methods.
+ */
+ public void onUpdated() {
+ // TODO(b/70983952): Fill this method in
+ refresh();
+ }
+
+ /**
+ * Result of the connect request indicated by the WifiEntry.CONNECT_STATUS constants.
+ */
+ public void onConnectResult(int status) {
+ // TODO(b/70983952): Fill this method in
+ }
+
+ /**
+ * Result of the disconnect request indicated by the WifiEntry.DISCONNECT_STATUS constants.
+ */
+ public void onDisconnectResult(int status) {
+ // TODO(b/70983952): Fill this method in
+ }
+
+ /**
+ * Result of the forget request indicated by the WifiEntry.FORGET_STATUS constants.
+ */
+ public void onForgetResult(int status) {
+ // TODO(b/70983952): Fill this method in
+ }
+
+ /**
+ * Result of the sign-in request indecated by the WifiEntry.SIGNIN_STATUS constants
+ */
+ public void onSignInResult(int status) {
+ // TODO(b/70983952): Fill this method in
+ }
+
+
+ private void updateIcon(int level) {
+ if (level == -1) {
+ setIcon(null);
+ return;
+ }
+
+ final Drawable drawable = mIconInjector.getIcon(level);
+ if (drawable != null) {
+ drawable.setTintList(Utils.getColorAttr(getContext(),
+ android.R.attr.colorControlNormal));
+ setIcon(drawable);
+ } else {
+ setIcon(null);
+ }
+ }
+
+ @Nullable
+ private StateListDrawable getFrictionStateListDrawable() {
+ TypedArray frictionSld;
+ try {
+ frictionSld = getContext().getTheme().obtainStyledAttributes(FRICTION_ATTRS);
+ } catch (Resources.NotFoundException e) {
+ // Fallback for platforms that do not need friction icon resources.
+ frictionSld = null;
+ }
+ return frictionSld != null ? (StateListDrawable) frictionSld.getDrawable(0) : null;
+ }
+
+ /**
+ * Binds the friction icon drawable using a StateListDrawable.
+ *
+ * <p>Friction icons will be rebound when notifyChange() is called, and therefore
+ * do not need to be managed in refresh()</p>.
+ */
+ private void bindFrictionImage(ImageView frictionImageView) {
+ if (frictionImageView == null || mFrictionSld == null) {
+ return;
+ }
+ if ((mWifiEntry.getSecurity() != WifiEntry.SECURITY_NONE)
+ && (mWifiEntry.getSecurity() != WifiEntry.SECURITY_OWE)) {
+ mFrictionSld.setState(STATE_SECURED);
+ }
+ frictionImageView.setImageDrawable(mFrictionSld.getCurrent());
+ }
+
+ /**
+ * Helper method to generate content description string.
+ */
+ @VisibleForTesting
+ CharSequence buildContentDescription() {
+ final Context context = getContext();
+
+ CharSequence contentDescription = getTitle();
+ final CharSequence summary = getSummary();
+ if (!TextUtils.isEmpty(summary)) {
+ contentDescription = TextUtils.concat(contentDescription, ",", summary);
+ }
+ int level = mWifiEntry.getLevel();
+ if (level >= 0 && level < WIFI_CONNECTION_STRENGTH.length) {
+ contentDescription = TextUtils.concat(contentDescription, ",",
+ context.getString(WIFI_CONNECTION_STRENGTH[level]));
+ }
+ return TextUtils.concat(contentDescription, ",",
+ mWifiEntry.getSecurity() == WifiEntry.SECURITY_NONE
+ ? context.getString(R.string.accessibility_wifi_security_type_none)
+ : context.getString(R.string.accessibility_wifi_security_type_secured));
+ }
+
+
+ static class IconInjector {
+ private final Context mContext;
+
+ IconInjector(Context context) {
+ mContext = context;
+ }
+
+ public Drawable getIcon(int level) {
+ return mContext.getDrawable(Utils.getWifiIconResource(level));
+ }
+ }
+
+ /**
+ * Set listeners, who want to listen the button client event.
+ */
+ public void setOnButtonClickListener(OnButtonClickListener listener) {
+ mOnButtonClickListener = listener;
+ notifyChanged();
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (view.getId() == R.id.icon_button) {
+ if (mOnButtonClickListener != null) {
+ mOnButtonClickListener.onButtonClick(this);
+ }
+ }
+ }
+
+ /**
+ * Callback to inform the caller that the icon button is clicked.
+ */
+ public interface OnButtonClickListener {
+
+ /**
+ * Register to listen the button click event.
+ */
+ void onButtonClick(WifiEntryPreference preference);
+ }
+
+ private Drawable getDrawable(@DrawableRes int iconResId) {
+ Drawable buttonIcon = null;
+
+ try {
+ buttonIcon = getContext().getDrawable(iconResId);
+ } catch (Resources.NotFoundException exception) {
+ // Do nothing
+ }
+ return buttonIcon;
+ }
+
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiSavedConfigUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiSavedConfigUtils.java
index 19e38081fcad..65c7786235bf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiSavedConfigUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiSavedConfigUtils.java
@@ -65,5 +65,17 @@ public class WifiSavedConfigUtils {
}
return savedConfigs;
}
+
+ /**
+ * Returns the count of the saved configurations on the device, including both Wi-Fi networks
+ * and Passpoint profiles.
+ *
+ * @param context The application context
+ * @param wifiManager An instance of {@link WifiManager}
+ * @return count of saved Wi-Fi networks
+ */
+ public static int getAllConfigsCount(Context context, WifiManager wifiManager) {
+ return getAllConfigs(context, wifiManager).size();
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 8bd5f57f9b71..b7ae3dca5c16 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -17,6 +17,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
@@ -28,7 +29,6 @@ import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiNetworkScoreCache;
-import android.net.wifi.WifiSsid;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
@@ -37,7 +37,10 @@ import com.android.settingslib.R;
import java.util.List;
-public class WifiStatusTracker extends ConnectivityManager.NetworkCallback {
+/**
+ * Track status of Wi-Fi for the Sys UI.
+ */
+public class WifiStatusTracker {
private final Context mContext;
private final WifiNetworkScoreCache mWifiNetworkScoreCache;
private final WifiManager mWifiManager;
@@ -56,8 +59,9 @@ public class WifiStatusTracker extends ConnectivityManager.NetworkCallback {
.clearCapabilities()
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();
- private final ConnectivityManager.NetworkCallback mNetworkCallback = new ConnectivityManager
- .NetworkCallback() {
+ private final NetworkCallback mNetworkCallback = new NetworkCallback() {
+ // Note: onCapabilitiesChanged is guaranteed to be called "immediately" after onAvailable
+ // and onLinkPropertiesChanged.
@Override
public void onCapabilitiesChanged(
Network network, NetworkCapabilities networkCapabilities) {
@@ -65,10 +69,35 @@ public class WifiStatusTracker extends ConnectivityManager.NetworkCallback {
mCallback.run();
}
};
+ private final NetworkCallback mDefaultNetworkCallback = new NetworkCallback() {
+ @Override
+ public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
+ // network is now the default network, and its capabilities are nc.
+ // This method will always be called immediately after the network becomes the
+ // default, in addition to any time the capabilities change while the network is
+ // the default.
+ mDefaultNetwork = network;
+ mDefaultNetworkCapabilities = nc;
+ updateStatusLabel();
+ mCallback.run();
+ }
+ @Override
+ public void onLost(Network network) {
+ // The system no longer has a default network.
+ mDefaultNetwork = null;
+ mDefaultNetworkCapabilities = null;
+ updateStatusLabel();
+ mCallback.run();
+ }
+ };
+ private Network mDefaultNetwork = null;
+ private NetworkCapabilities mDefaultNetworkCapabilities = null;
private final Runnable mCallback;
private WifiInfo mWifiInfo;
public boolean enabled;
+ public boolean isCaptivePortal;
+ public boolean isDefaultNetwork;
public int state;
public boolean connected;
public String ssid;
@@ -94,14 +123,45 @@ public class WifiStatusTracker extends ConnectivityManager.NetworkCallback {
mWifiNetworkScoreCache.registerListener(mCacheListener);
mConnectivityManager.registerNetworkCallback(
mNetworkRequest, mNetworkCallback, mHandler);
+ mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback, mHandler);
} else {
mNetworkScoreManager.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI,
mWifiNetworkScoreCache);
mWifiNetworkScoreCache.unregisterListener();
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+ mConnectivityManager.unregisterNetworkCallback(mDefaultNetworkCallback);
}
}
+ /**
+ * Fetches initial state as if a WifiManager.NETWORK_STATE_CHANGED_ACTION have been received.
+ * This replaces the dependency on the initial sticky broadcast.
+ */
+ public void fetchInitialState() {
+ if (mWifiManager == null) {
+ return;
+ }
+ updateWifiState();
+ final NetworkInfo networkInfo =
+ mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+ connected = networkInfo != null && networkInfo.isConnected();
+ mWifiInfo = null;
+ ssid = null;
+ if (connected) {
+ mWifiInfo = mWifiManager.getConnectionInfo();
+ if (mWifiInfo != null) {
+ if (mWifiInfo.isPasspointAp() || mWifiInfo.isOsuAp()) {
+ ssid = mWifiInfo.getPasspointProviderFriendlyName();
+ } else {
+ ssid = getValidSsid(mWifiInfo);
+ }
+ updateRssi(mWifiInfo.getRssi());
+ maybeRequestNetworkScore();
+ }
+ }
+ updateStatusLabel();
+ }
+
public void handleBroadcast(Intent intent) {
if (mWifiManager == null) {
return;
@@ -143,7 +203,7 @@ public class WifiStatusTracker extends ConnectivityManager.NetworkCallback {
private void updateRssi(int newRssi) {
rssi = newRssi;
- level = WifiManager.calculateSignalLevel(rssi, WifiManager.RSSI_LEVELS);
+ level = mWifiManager.calculateSignalLevel(rssi);
}
private void maybeRequestNetworkScore() {
@@ -154,11 +214,25 @@ public class WifiStatusTracker extends ConnectivityManager.NetworkCallback {
}
private void updateStatusLabel() {
- final NetworkCapabilities networkCapabilities
- = mConnectivityManager.getNetworkCapabilities(mWifiManager.getCurrentNetwork());
+ if (mWifiManager == null) {
+ return;
+ }
+ NetworkCapabilities networkCapabilities;
+ final Network currentWifiNetwork = mWifiManager.getCurrentNetwork();
+ if (currentWifiNetwork != null && currentWifiNetwork.equals(mDefaultNetwork)) {
+ // Wifi is connected and the default network.
+ isDefaultNetwork = true;
+ networkCapabilities = mDefaultNetworkCapabilities;
+ } else {
+ isDefaultNetwork = false;
+ networkCapabilities = mConnectivityManager.getNetworkCapabilities(
+ mWifiManager.getCurrentNetwork());
+ }
+ isCaptivePortal = false;
if (networkCapabilities != null) {
if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) {
statusLabel = mContext.getString(R.string.wifi_status_sign_in_required);
+ isCaptivePortal = true;
return;
} else if (networkCapabilities.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY)) {
statusLabel = mContext.getString(R.string.wifi_limited_connection);
@@ -189,7 +263,7 @@ public class WifiStatusTracker extends ConnectivityManager.NetworkCallback {
private String getValidSsid(WifiInfo info) {
String ssid = info.getSSID();
- if (ssid != null && !WifiSsid.NONE.equals(ssid)) {
+ if (ssid != null && !WifiManager.UNKNOWN_SSID.equals(ssid)) {
return ssid;
}
// OK, it's not in the connectionInfo; we have to go hunting for it
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index d107bc33aa86..bf5ab1c9951a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -77,7 +77,16 @@ import java.util.stream.Collectors;
/**
* Tracks saved or available wifi networks and their state.
+ *
+ * @deprecated WifiTracker/AccessPoint is no longer supported, and will be removed in a future
+ * release. Clients that need a dynamic list of available wifi networks should migrate to one of the
+ * newer tracker classes,
+ * {@link com.android.wifitrackerlib.WifiPickerTracker},
+ * {@link com.android.wifitrackerlib.SavedNetworkTracker},
+ * {@link com.android.wifitrackerlib.NetworkDetailsTracker},
+ * in conjunction with {@link com.android.wifitrackerlib.WifiEntry} to represent each wifi network.
*/
+@Deprecated
public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestroy {
/**
* Default maximum age in millis of cached scored networks in
@@ -182,7 +191,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro
filter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
filter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
filter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
- filter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
+ filter.addAction(WifiManager.ACTION_LINK_CONFIGURATION_CHANGED);
filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
@@ -226,7 +235,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro
mConnectivityManager = connectivityManager;
// check if verbose logging developer option has been turned on or off
- sVerboseLogging = mWifiManager != null && (mWifiManager.getVerboseLoggingLevel() > 0);
+ sVerboseLogging = mWifiManager != null && mWifiManager.isVerboseLoggingEnabled();
mFilter = filter;
@@ -518,8 +527,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro
int networkId, final List<WifiConfiguration> configs) {
if (configs != null) {
for (WifiConfiguration config : configs) {
- if (mLastInfo != null && networkId == config.networkId &&
- !(config.selfAdded && config.numAssociation == 0)) {
+ if (mLastInfo != null && networkId == config.networkId) {
return config;
}
}
@@ -607,7 +615,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro
List<ScanResult> cachedScanResults = new ArrayList<>(mScanResultCache.values());
- // Add a unique Passpoint AccessPoint for each Passpoint profile's FQDN.
+ // Add a unique Passpoint AccessPoint for each Passpoint profile's unique identifier.
accessPoints.addAll(updatePasspointAccessPoints(
mWifiManager.getAllMatchingWifiConfigs(cachedScanResults), cachedAccessPoints));
@@ -883,7 +891,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro
fetchScansAndConfigsAndUpdateAccessPoints();
} else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action)
- || WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
+ || WifiManager.ACTION_LINK_CONFIGURATION_CHANGED.equals(action)) {
fetchScansAndConfigsAndUpdateAccessPoints();
} else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
// TODO(sghuman): Refactor these methods so they cannot result in duplicate
@@ -892,9 +900,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro
updateNetworkInfo(info);
fetchScansAndConfigsAndUpdateAccessPoints();
} else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
- NetworkInfo info =
- mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
- updateNetworkInfo(info);
+ updateNetworkInfo(/* networkInfo= */ null);
}
}
};
@@ -940,7 +946,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro
// We don't send a NetworkInfo object along with this message, because even if we
// fetch one from ConnectivityManager, it might be older than the most recent
// NetworkInfo message we got via a WIFI_STATE_CHANGED broadcast.
- updateNetworkInfo(null);
+ updateNetworkInfo(/* networkInfo= */ null);
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
index 4e6c005457c0..0e6a60bf47c1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
@@ -16,9 +16,13 @@
package com.android.settingslib.wifi;
+import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED;
+import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.getMaxNetworkSelectionDisableReason;
+
import android.content.Context;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
import android.net.wifi.WifiInfo;
import android.os.SystemClock;
@@ -30,6 +34,8 @@ import java.util.Map;
public class WifiUtils {
+ private static final int INVALID_RSSI = -127;
+
public static String buildLoggingSummary(AccessPoint accessPoint, WifiConfiguration config) {
final StringBuilder summary = new StringBuilder();
final WifiInfo info = accessPoint.getInfo();
@@ -39,7 +45,9 @@ public class WifiUtils {
summary.append(" f=" + Integer.toString(info.getFrequency()));
}
summary.append(" " + getVisibilityStatus(accessPoint));
- if (config != null && !config.getNetworkSelectionStatus().isNetworkEnabled()) {
+ if (config != null
+ && (config.getNetworkSelectionStatus().getNetworkSelectionStatus()
+ != NETWORK_SELECTION_ENABLED)) {
summary.append(" (" + config.getNetworkSelectionStatus().getNetworkStatusString());
if (config.getNetworkSelectionStatus().getDisableTime() > 0) {
long now = System.currentTimeMillis();
@@ -56,15 +64,14 @@ public class WifiUtils {
}
if (config != null) {
- WifiConfiguration.NetworkSelectionStatus networkStatus =
- config.getNetworkSelectionStatus();
- for (int index = WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE;
- index < WifiConfiguration.NetworkSelectionStatus
- .NETWORK_SELECTION_DISABLED_MAX; index++) {
- if (networkStatus.getDisableReasonCounter(index) != 0) {
- summary.append(" " + WifiConfiguration.NetworkSelectionStatus
- .getNetworkDisableReasonString(index) + "="
- + networkStatus.getDisableReasonCounter(index));
+ NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
+ for (int reason = 0; reason <= getMaxNetworkSelectionDisableReason(); reason++) {
+ if (networkStatus.getDisableReasonCounter(reason) != 0) {
+ summary.append(" ")
+ .append(NetworkSelectionStatus
+ .getNetworkSelectionDisableReasonString(reason))
+ .append("=")
+ .append(networkStatus.getDisableReasonCounter(reason));
}
}
}
@@ -93,20 +100,21 @@ public class WifiUtils {
if (bssid != null) {
visibility.append(" ").append(bssid);
}
+ visibility.append(" standard = ").append(info.getWifiStandard());
visibility.append(" rssi=").append(info.getRssi());
visibility.append(" ");
- visibility.append(" score=").append(info.score);
+ visibility.append(" score=").append(info.getScore());
if (accessPoint.getSpeed() != AccessPoint.Speed.NONE) {
visibility.append(" speed=").append(accessPoint.getSpeedLabel());
}
- visibility.append(String.format(" tx=%.1f,", info.txSuccessRate));
- visibility.append(String.format("%.1f,", info.txRetriesRate));
- visibility.append(String.format("%.1f ", info.txBadRate));
- visibility.append(String.format("rx=%.1f", info.rxSuccessRate));
+ visibility.append(String.format(" tx=%.1f,", info.getSuccessfulTxPacketsPerSecond()));
+ visibility.append(String.format("%.1f,", info.getRetriedTxPacketsPerSecond()));
+ visibility.append(String.format("%.1f ", info.getLostTxPacketsPerSecond()));
+ visibility.append(String.format("rx=%.1f", info.getSuccessfulRxPacketsPerSecond()));
}
- int maxRssi5 = WifiConfiguration.INVALID_RSSI;
- int maxRssi24 = WifiConfiguration.INVALID_RSSI;
+ int maxRssi5 = INVALID_RSSI;
+ int maxRssi24 = INVALID_RSSI;
final int maxDisplayedScans = 4;
int num5 = 0; // number of scanned BSSID on 5GHz band
int num24 = 0; // number of scanned BSSID on 2.4Ghz band