Merge RQ2A.210305.007

Bug: 180401296
Merged-In: I12e20519ac29f802f9983dcf27f313388b33829c
Change-Id: I8ca0bb7173f6abe08596422b281f6913b66ab9b7
This commit is contained in:
Xin Li
2021-03-02 15:49:43 -08:00
322 changed files with 32241 additions and 48985 deletions

View File

@@ -88,6 +88,7 @@ public class ResetNetworkConfirm extends InstrumentedFragment {
@Override
protected Boolean doInBackground(Void... params) {
boolean isResetSucceed = true;
ConnectivityManager connectivityManager = (ConnectivityManager)
mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager != null) {
@@ -107,6 +108,10 @@ public class ResetNetworkConfirm extends InstrumentedFragment {
p2pFactoryReset(mContext);
if (mEraseEsim) {
isResetSucceed = RecoverySystem.wipeEuiccData(mContext, mPackageName);
}
TelephonyManager telephonyManager = (TelephonyManager)
mContext.getSystemService(TelephonyManager.class)
.createForSubscriptionId(mSubId);
@@ -131,11 +136,7 @@ public class ResetNetworkConfirm extends InstrumentedFragment {
}
restoreDefaultApn(mContext);
if (mEraseEsim) {
return RecoverySystem.wipeEuiccData(mContext, mPackageName);
} else {
return true;
}
return isResetSucceed;
}
@Override

View File

@@ -273,6 +273,15 @@ public final class Utils extends com.android.settingslib.Utils {
return batteryChangedIntent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true);
}
/**
* Return true if battery is present.
*/
public static boolean isBatteryPresent(Context context) {
Intent batteryBroadcast = context.registerReceiver(null /* receiver */,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
return isBatteryPresent(batteryBroadcast);
}
public static String getBatteryPercentage(Intent batteryChangedIntent) {
return formatPercentage(getBatteryLevel(batteryChangedIntent));
}

View File

@@ -72,6 +72,9 @@ public class UsbConnectionBroadcastReceiver extends BroadcastReceiver implements
if (intent.getExtras().getBoolean(UsbManager.USB_FUNCTION_RNDIS)) {
functions |= UsbManager.FUNCTION_RNDIS;
}
if (intent.getExtras().getBoolean(UsbManager.USB_FUNCTION_ACCESSORY)) {
functions |= UsbManager.FUNCTION_ACCESSORY;
}
mFunctions = functions;
mDataRole = mUsbBackend.getDataRole();
mPowerRole = mUsbBackend.getPowerRole();

View File

@@ -22,6 +22,7 @@ import static android.net.ConnectivityManager.TETHERING_USB;
import android.content.Context;
import android.hardware.usb.UsbManager;
import android.net.ConnectivityManager;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceCategory;
@@ -40,6 +41,9 @@ import java.util.Map;
public class UsbDetailsFunctionsController extends UsbDetailsController
implements RadioButtonPreference.OnClickListener {
private static final String TAG = "UsbFunctionsCtrl";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
static final Map<Long, Integer> FUNCTIONS_MAP = new LinkedHashMap<>();
static {
@@ -88,6 +92,10 @@ public class UsbDetailsFunctionsController extends UsbDetailsController
@Override
protected void refresh(boolean connected, long functions, int powerRole, int dataRole) {
if (DEBUG) {
Log.d(TAG, "refresh() connected : " + connected + ", functions : " + functions
+ ", powerRole : " + powerRole + ", dataRole : " + dataRole);
}
if (!connected || dataRole != DATA_ROLE_DEVICE) {
mProfilesContainer.setEnabled(false);
} else {
@@ -100,7 +108,11 @@ public class UsbDetailsFunctionsController extends UsbDetailsController
pref = getProfilePreference(UsbBackend.usbFunctionsToString(option), title);
// Only show supported options
if (mUsbBackend.areFunctionsSupported(option)) {
pref.setChecked(functions == option);
if (isAccessoryMode(functions)) {
pref.setChecked(UsbManager.FUNCTION_MTP == option);
} else {
pref.setChecked(functions == option);
}
} else {
mProfilesContainer.removePreference(pref);
}
@@ -111,7 +123,14 @@ public class UsbDetailsFunctionsController extends UsbDetailsController
public void onRadioButtonClicked(RadioButtonPreference preference) {
final long function = UsbBackend.usbFunctionsFromString(preference.getKey());
final long previousFunction = mUsbBackend.getCurrentFunctions();
if (function != previousFunction && !Utils.isMonkeyRunning()) {
if (DEBUG) {
Log.d(TAG, "onRadioButtonClicked() function : " + function + ", toString() : "
+ UsbManager.usbFunctionsToString(function) + ", previousFunction : "
+ previousFunction + ", toString() : "
+ UsbManager.usbFunctionsToString(previousFunction));
}
if (function != previousFunction && !Utils.isMonkeyRunning()
&& !shouldIgnoreClickEvent(function, previousFunction)) {
mPreviousFunction = previousFunction;
//Update the UI in advance to make it looks smooth
@@ -134,6 +153,14 @@ public class UsbDetailsFunctionsController extends UsbDetailsController
}
}
private boolean shouldIgnoreClickEvent(long function, long previousFunction) {
return isAccessoryMode(previousFunction) && function == UsbManager.FUNCTION_MTP;
}
private boolean isAccessoryMode(long function) {
return (function & UsbManager.FUNCTION_ACCESSORY) != 0;
}
@Override
public boolean isAvailable() {
return !Utils.isMonkeyRunning();

View File

@@ -41,11 +41,14 @@ import androidx.fragment.app.FragmentActivity;
import com.android.settings.R;
import com.android.settings.SubSettings;
import com.android.settings.dashboard.CategoryManager;
import com.android.settingslib.drawer.Tile;
import com.google.android.setupcompat.util.WizardManagerHelper;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class SettingsBaseActivity extends FragmentActivity {
@@ -59,6 +62,7 @@ public class SettingsBaseActivity extends FragmentActivity {
private final PackageReceiver mPackageReceiver = new PackageReceiver();
private final List<CategoryListener> mCategoryListeners = new ArrayList<>();
private int mCategoriesUpdateTaskCount;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -147,10 +151,10 @@ public class SettingsBaseActivity extends FragmentActivity {
((ViewGroup) findViewById(R.id.content_frame)).addView(view, params);
}
private void onCategoriesChanged() {
private void onCategoriesChanged(Set<String> categories) {
final int N = mCategoryListeners.size();
for (int i = 0; i < N; i++) {
mCategoryListeners.get(i).onCategoriesChanged();
mCategoryListeners.get(i).onCategoriesChanged(categories);
}
}
@@ -194,38 +198,100 @@ public class SettingsBaseActivity extends FragmentActivity {
* Updates dashboard categories. Only necessary to call this after setTileEnabled
*/
public void updateCategories() {
new CategoriesUpdateTask().execute();
updateCategories(false /* fromBroadcast */);
}
private void updateCategories(boolean fromBroadcast) {
// Only allow at most 2 tasks existing at the same time since when the first one is
// executing, there may be new data from the second update request.
// Ignore the third update request because the second task is still waiting for the first
// task to complete in a serial thread, which will get the latest data.
if (mCategoriesUpdateTaskCount < 2) {
new CategoriesUpdateTask().execute(fromBroadcast);
}
}
public interface CategoryListener {
void onCategoriesChanged();
/**
* @param categories the changed categories that have to be refreshed, or null to force
* refreshing all.
*/
void onCategoriesChanged(@Nullable Set<String> categories);
}
private class CategoriesUpdateTask extends AsyncTask<Void, Void, Void> {
private class CategoriesUpdateTask extends AsyncTask<Boolean, Void, Set<String>> {
private final Context mContext;
private final CategoryManager mCategoryManager;
private Map<ComponentName, Tile> mPreviousTileMap;
public CategoriesUpdateTask() {
mCategoryManager = CategoryManager.get(SettingsBaseActivity.this);
mCategoriesUpdateTaskCount++;
mContext = SettingsBaseActivity.this;
mCategoryManager = CategoryManager.get(mContext);
}
@Override
protected Void doInBackground(Void... params) {
mCategoryManager.reloadAllCategories(SettingsBaseActivity.this);
return null;
}
@Override
protected void onPostExecute(Void result) {
protected Set<String> doInBackground(Boolean... params) {
mPreviousTileMap = mCategoryManager.getTileByComponentMap();
mCategoryManager.reloadAllCategories(mContext);
mCategoryManager.updateCategoryFromBlacklist(sTileBlacklist);
onCategoriesChanged();
return getChangedCategories(params[0]);
}
@Override
protected void onPostExecute(Set<String> categories) {
if (categories == null || !categories.isEmpty()) {
onCategoriesChanged(categories);
}
mCategoriesUpdateTaskCount--;
}
// Return the changed categories that have to be refreshed, or null to force refreshing all.
private Set<String> getChangedCategories(boolean fromBroadcast) {
if (!fromBroadcast) {
// Always refresh for non-broadcast case.
return null;
}
final Set<String> changedCategories = new ArraySet<>();
final Map<ComponentName, Tile> currentTileMap =
mCategoryManager.getTileByComponentMap();
currentTileMap.forEach((component, currentTile) -> {
final Tile previousTile = mPreviousTileMap.get(component);
// Check if the tile is newly added.
if (previousTile == null) {
Log.i(TAG, "Tile added: " + component.flattenToShortString());
changedCategories.add(currentTile.getCategory());
return;
}
// Check if the title or summary has changed.
if (!TextUtils.equals(currentTile.getTitle(mContext),
previousTile.getTitle(mContext))
|| !TextUtils.equals(currentTile.getSummary(mContext),
previousTile.getSummary(mContext))) {
Log.i(TAG, "Tile changed: " + component.flattenToShortString());
changedCategories.add(currentTile.getCategory());
}
});
// Check if any previous tile is removed.
final Set<ComponentName> removal = new ArraySet(mPreviousTileMap.keySet());
removal.removeAll(currentTileMap.keySet());
removal.forEach(component -> {
Log.i(TAG, "Tile removed: " + component.flattenToShortString());
changedCategories.add(mPreviousTileMap.get(component).getCategory());
});
return changedCategories;
}
}
private class PackageReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
updateCategories();
updateCategories(true /* fromBroadcast */);
}
}
}

View File

@@ -41,6 +41,7 @@ import java.util.Set;
public class CategoryManager {
private static final String TAG = "CategoryManager";
private static final boolean DEBUG = false;
private static CategoryManager sInstance;
private final InterestingConfigChanges mInterestingConfigChanges;
@@ -88,6 +89,7 @@ public class CategoryManager {
public synchronized void updateCategoryFromBlacklist(Set<ComponentName> tileBlacklist) {
if (mCategories == null) {
Log.w(TAG, "Category is null, skipping blacklist update");
return;
}
for (int i = 0; i < mCategories.size(); i++) {
DashboardCategory category = mCategories.get(i);
@@ -100,6 +102,31 @@ public class CategoryManager {
}
}
/** Return the current tile map */
public synchronized Map<ComponentName, Tile> getTileByComponentMap() {
final Map<ComponentName, Tile> result = new ArrayMap<>();
if (mCategories == null) {
Log.w(TAG, "Category is null, no tiles");
return result;
}
mCategories.forEach(category -> {
for (int i = 0; i < category.getTilesCount(); i++) {
final Tile tile = category.getTile(i);
result.put(tile.getIntent().getComponent(), tile);
}
});
return result;
}
private void logTiles(Context context) {
if (DEBUG) {
getTileByComponentMap().forEach((component, tile) -> {
Log.d(TAG, "Tile: " + tile.getCategory().replace("com.android.settings.", "")
+ ": " + tile.getTitle(context) + ", " + component.flattenToShortString());
});
}
}
private synchronized void tryInitCategories(Context context) {
// Keep cached tiles by default. The cache is only invalidated when InterestingConfigChange
// happens.
@@ -108,6 +135,7 @@ public class CategoryManager {
private synchronized void tryInitCategories(Context context, boolean forceClearCache) {
if (mCategories == null) {
final boolean firstLoading = mCategoryByKeyMap.isEmpty();
if (forceClearCache) {
mTileByComponentCache.clear();
}
@@ -119,6 +147,9 @@ public class CategoryManager {
backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap);
sortCategories(context, mCategoryByKeyMap);
filterDuplicateTiles(mCategoryByKeyMap);
if (firstLoading) {
logTiles(context);
}
}
}

View File

@@ -56,6 +56,7 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutionException;
/**
@@ -160,13 +161,21 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment
}
@Override
public void onCategoriesChanged() {
final DashboardCategory category =
mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());
if (category == null) {
public void onCategoriesChanged(Set<String> categories) {
final String categoryKey = getCategoryKey();
final DashboardCategory dashboardCategory =
mDashboardFeatureProvider.getTilesForCategory(categoryKey);
if (dashboardCategory == null) {
return;
}
refreshDashboardTiles(getLogTag());
if (categories == null) {
// force refreshing
refreshDashboardTiles(getLogTag());
} else if (categories.contains(categoryKey)) {
Log.i(TAG, "refresh tiles for " + categoryKey);
refreshDashboardTiles(getLogTag());
}
}
@Override

View File

@@ -21,9 +21,11 @@ import android.content.Context;
import android.provider.Settings;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
import com.android.internal.R;
import com.android.settings.Utils;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerMixin;
@@ -34,12 +36,27 @@ import com.android.settings.core.PreferenceControllerMixin;
public class BatteryPercentagePreferenceController extends BasePreferenceController implements
PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
private Preference mPreference;
public BatteryPercentagePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(getPreferenceKey());
if (!Utils.isBatteryPresent(mContext)) {
// Disable battery percentage
onPreferenceChange(mPreference, false /* newValue */);
}
}
@Override
public int getAvailabilityStatus() {
if (!Utils.isBatteryPresent(mContext)) {
return CONDITIONALLY_UNAVAILABLE;
}
return mContext.getResources().getBoolean(
R.bool.config_battery_percentage_setting_available) ? AVAILABLE
: UNSUPPORTED_ON_DEVICE;

View File

@@ -20,7 +20,9 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.PowerManager;
import android.util.Log;
import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
@@ -38,15 +40,18 @@ import java.lang.annotation.RetentionPolicy;
* 1. Battery level(e.g. 100%->99%)
* 2. Battery status(e.g. plugged->unplugged)
* 3. Battery saver(e.g. off->on)
* 4. Battery health(e.g. good->overheat)
*/
public class BatteryBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "BatteryBroadcastRcvr";
/**
* Callback when the following has been changed:
*
* Battery level(e.g. 100%->99%)
* Battery status(e.g. plugged->unplugged)
* Battery saver(e.g. off->on)
* Battery health(e.g. good->overheat)
*/
public interface OnBatteryChangedListener {
void onBatteryChanged(@BatteryUpdateType int type);
@@ -56,18 +61,24 @@ public class BatteryBroadcastReceiver extends BroadcastReceiver {
@IntDef({BatteryUpdateType.MANUAL,
BatteryUpdateType.BATTERY_LEVEL,
BatteryUpdateType.BATTERY_SAVER,
BatteryUpdateType.BATTERY_STATUS})
BatteryUpdateType.BATTERY_STATUS,
BatteryUpdateType.BATTERY_HEALTH,
BatteryUpdateType.BATTERY_NOT_PRESENT})
public @interface BatteryUpdateType {
int MANUAL = 0;
int BATTERY_LEVEL = 1;
int BATTERY_SAVER = 2;
int BATTERY_STATUS = 3;
int BATTERY_HEALTH = 4;
int BATTERY_NOT_PRESENT = 5;
}
@VisibleForTesting
String mBatteryLevel;
@VisibleForTesting
String mBatteryStatus;
@VisibleForTesting
int mBatteryHealth;
private OnBatteryChangedListener mBatteryListener;
private Context mContext;
@@ -101,10 +112,16 @@ public class BatteryBroadcastReceiver extends BroadcastReceiver {
if (intent != null && mBatteryListener != null) {
if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
final String batteryLevel = Utils.getBatteryPercentage(intent);
final String batteryStatus = Utils.getBatteryStatus(
mContext, intent);
if (forceUpdate) {
final String batteryStatus = Utils.getBatteryStatus(mContext, intent);
final int batteryHealth = intent.getIntExtra(
BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_UNKNOWN);
if (!Utils.isBatteryPresent(intent)) {
Log.w(TAG, "Problem reading the battery meter.");
mBatteryListener.onBatteryChanged(BatteryUpdateType.BATTERY_NOT_PRESENT);
} else if (forceUpdate) {
mBatteryListener.onBatteryChanged(BatteryUpdateType.MANUAL);
} else if (batteryHealth != mBatteryHealth) {
mBatteryListener.onBatteryChanged(BatteryUpdateType.BATTERY_HEALTH);
} else if(!batteryLevel.equals(mBatteryLevel)) {
mBatteryListener.onBatteryChanged(BatteryUpdateType.BATTERY_LEVEL);
} else if (!batteryStatus.equals(mBatteryStatus)) {
@@ -112,6 +129,7 @@ public class BatteryBroadcastReceiver extends BroadcastReceiver {
}
mBatteryLevel = batteryLevel;
mBatteryStatus = batteryStatus;
mBatteryHealth = batteryHealth;
} else if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) {
mBatteryListener.onBatteryChanged(BatteryUpdateType.BATTERY_SAVER);
}

View File

@@ -25,6 +25,10 @@ import android.icu.text.NumberFormat;
import android.os.BatteryManager;
import android.os.PowerManager;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.VisibleForTesting;
@@ -35,7 +39,9 @@ import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.utils.AnnotationSpan;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.Utils;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
@@ -49,6 +55,7 @@ public class BatteryHeaderPreferenceController extends BasePreferenceController
implements PreferenceControllerMixin, LifecycleObserver, OnStart {
@VisibleForTesting
static final String KEY_BATTERY_HEADER = "battery_header";
private static final String ANNOTATION_URL = "url";
@VisibleForTesting
BatteryStatusFeatureProvider mBatteryStatusFeatureProvider;
@@ -94,7 +101,11 @@ public class BatteryHeaderPreferenceController extends BasePreferenceController
mBatteryPercentText = mBatteryLayoutPref.findViewById(R.id.battery_percent);
mSummary1 = mBatteryLayoutPref.findViewById(R.id.summary1);
quickUpdateHeaderPreference();
if (com.android.settings.Utils.isBatteryPresent(mContext)) {
quickUpdateHeaderPreference();
} else {
showHelpMessage();
}
}
@Override
@@ -113,7 +124,9 @@ public class BatteryHeaderPreferenceController extends BasePreferenceController
public void updateHeaderPreference(BatteryInfo info) {
mBatteryPercentText.setText(formatBatteryPercentageText(info.batteryLevel));
if (!mBatteryStatusFeatureProvider.triggerBatteryStatusUpdate(this, info)) {
if (info.remainingLabel == null) {
if (BatteryUtils.isBatteryDefenderOn(info)) {
mSummary1.setText(null);
} else if (info.remainingLabel == null) {
mSummary1.setText(info.statusLabel);
} else {
mSummary1.setText(info.remainingLabel);
@@ -146,6 +159,32 @@ public class BatteryHeaderPreferenceController extends BasePreferenceController
mBatteryPercentText.setText(formatBatteryPercentageText(batteryLevel));
}
@VisibleForTesting
void showHelpMessage() {
final LinearLayout batteryInfoLayout =
mBatteryLayoutPref.findViewById(R.id.battery_info_layout);
// Remove battery meter icon
mBatteryMeterView.setVisibility(View.GONE);
// Update the width of battery info layout
final ViewGroup.LayoutParams params = batteryInfoLayout.getLayoutParams();
params.width = LinearLayout.LayoutParams.WRAP_CONTENT;
batteryInfoLayout.setLayoutParams(params);
mBatteryPercentText.setText(mContext.getText(R.string.unknown));
// Add linkable text for learn more
final Intent helpIntent = HelpUtils.getHelpIntent(mContext,
mContext.getString(R.string.help_url_battery_missing),
mContext.getClass().getName());
final AnnotationSpan.LinkInfo linkInfo = new AnnotationSpan
.LinkInfo(mContext, ANNOTATION_URL, helpIntent);
if (linkInfo.isActionable()) {
mSummary1.setMovementMethod(LinkMovementMethod.getInstance());
mSummary1.setText(AnnotationSpan
.linkify(mContext.getText(R.string.battery_missing_help_message), linkInfo));
} else {
mSummary1.setText(mContext.getText(R.string.battery_missing_message));
}
}
private CharSequence formatBatteryPercentageText(int batteryLevel) {
return TextUtils.expandTemplate(mContext.getText(R.string.battery_header_title_alternate),
NumberFormat.getIntegerInstance().format(batteryLevel));

View File

@@ -45,6 +45,7 @@ public class BatteryInfo {
public CharSequence remainingLabel;
public int batteryLevel;
public boolean discharging = true;
public boolean isOverheated;
public long remainingTimeUs = 0;
public long averageTimeToDischarge = EstimateKt.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN;
public String batteryPercentString;
@@ -232,6 +233,9 @@ public class BatteryInfo {
info.batteryPercentString = Utils.formatPercentage(info.batteryLevel);
info.mCharging = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
info.averageTimeToDischarge = estimate.getAverageDischargeTime();
info.isOverheated = batteryBroadcast.getIntExtra(
BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_UNKNOWN)
== BatteryManager.BATTERY_HEALTH_OVERHEAT;
info.statusLabel = Utils.getBatteryStatus(context, batteryBroadcast);
if (!info.mCharging) {
@@ -251,7 +255,12 @@ public class BatteryInfo {
BatteryManager.BATTERY_STATUS_UNKNOWN);
info.discharging = false;
info.suggestionLabel = null;
if (chargeTime > 0 && status != BatteryManager.BATTERY_STATUS_FULL) {
if (info.isOverheated && status != BatteryManager.BATTERY_STATUS_FULL) {
info.remainingLabel = null;
int chargingLimitedResId = R.string.power_charging_limited;
info.chargeLabel =
context.getString(chargingLimitedResId, info.batteryPercentString);
} else if (chargeTime > 0 && status != BatteryManager.BATTERY_STATUS_FULL) {
info.remainingTimeUs = chargeTime;
CharSequence timeString = StringUtil.formatElapsedTime(context,
PowerUtil.convertUsToMs(info.remainingTimeUs), false /* withSeconds */);

View File

@@ -19,8 +19,6 @@ package com.android.settings.fuelgauge;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.util.AttributeSet;
import android.widget.ImageView;
@@ -52,14 +50,12 @@ public class BatteryMeterView extends ImageView {
super(context, attrs, defStyleAttr);
final int frameColor = context.getColor(R.color.meter_background_color);
mAccentColorFilter = new PorterDuffColorFilter(
Utils.getColorAttrDefaultColor(context, android.R.attr.colorAccent),
PorterDuff.Mode.SRC);
mErrorColorFilter = new PorterDuffColorFilter(
context.getColor(R.color.battery_icon_color_error), PorterDuff.Mode.SRC_IN);
mForegroundColorFilter =new PorterDuffColorFilter(
Utils.getColorAttrDefaultColor(context, android.R.attr.colorForeground),
PorterDuff.Mode.SRC);
mAccentColorFilter = Utils.getAlphaInvariantColorFilterForColor(
Utils.getColorAttrDefaultColor(context, android.R.attr.colorAccent));
mErrorColorFilter = Utils.getAlphaInvariantColorFilterForColor(
context.getColor(R.color.battery_icon_color_error));
mForegroundColorFilter = Utils.getAlphaInvariantColorFilterForColor(
Utils.getColorAttrDefaultColor(context, android.R.attr.colorForeground));
mDrawable = new BatteryMeterDrawable(context, frameColor);
mDrawable.setColorFilter(mAccentColorFilter);
setImageDrawable(mDrawable);

View File

@@ -403,6 +403,13 @@ public class BatteryUtils {
Log.d(tag, message + ": " + (System.currentTimeMillis() - startTime) + "ms");
}
/**
* Return {@code true} if battery is overheated and charging.
*/
public static boolean isBatteryDefenderOn(BatteryInfo batteryInfo) {
return batteryInfo.isOverheated && !batteryInfo.discharging;
}
/**
* Find package uid from package name
*

View File

@@ -44,6 +44,7 @@ public abstract class PowerUsageBase extends DashboardFragment {
protected BatteryStatsHelper mStatsHelper;
protected UserManager mUm;
private BatteryBroadcastReceiver mBatteryBroadcastReceiver;
protected boolean mIsBatteryPresent = true;
@Override
public void onAttach(Activity activity) {
@@ -60,6 +61,9 @@ public abstract class PowerUsageBase extends DashboardFragment {
mBatteryBroadcastReceiver = new BatteryBroadcastReceiver(getContext());
mBatteryBroadcastReceiver.setBatteryChangedListener(type -> {
if (type == BatteryBroadcastReceiver.BatteryUpdateType.BATTERY_NOT_PRESENT) {
mIsBatteryPresent = false;
}
restartBatteryStatsLoader(type);
});
}

View File

@@ -56,8 +56,8 @@ import com.android.settingslib.widget.LayoutPreference;
import java.util.List;
/**
* Displays a list of apps and subsystems that consume power, ordered by how much power was
* consumed since the last time it was unplugged.
* Displays a list of apps and subsystems that consume power, ordered by how much power was consumed
* since the last time it was unplugged.
*/
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class PowerUsageSummary extends PowerUsageBase implements OnLongClickListener,
@@ -220,7 +220,9 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList
KEY_TIME_SINCE_LAST_FULL_CHARGE);
mBatteryUtils = BatteryUtils.getInstance(getContext());
restartBatteryInfoLoader();
if (Utils.isBatteryPresent(getContext())) {
restartBatteryInfoLoader();
}
mBatteryTipPreferenceController.restoreInstanceState(icicle);
updateBatteryTipFlag(icicle);
}
@@ -287,6 +289,10 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList
if (context == null) {
return;
}
// Skip refreshing UI if battery is not present.
if (!mIsBatteryPresent) {
return;
}
// Skip BatteryTipLoader if device is rotated or only battery level change
if (mNeedUpdateBatteryTip
@@ -295,7 +301,6 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList
} else {
mNeedUpdateBatteryTip = true;
}
// reload BatteryInfo and updateUI
restartBatteryInfoLoader();
updateLastFullChargePreference();
@@ -354,6 +359,10 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList
if (getContext() == null) {
return;
}
// Skip restartBatteryInfoLoader if battery is not present.
if (!mIsBatteryPresent) {
return;
}
getLoaderManager().restartLoader(BATTERY_INFO_LOADER, Bundle.EMPTY,
mBatteryInfoLoaderCallbacks);
if (mPowerFeatureProvider.isEstimateDebugEnabled()) {
@@ -378,7 +387,10 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList
@Override
protected void restartBatteryStatsLoader(@BatteryUpdateType int refreshType) {
super.restartBatteryStatsLoader(refreshType);
mBatteryHeaderPreferenceController.quickUpdateHeaderPreference();
// Update battery header if battery is present.
if (mIsBatteryPresent) {
mBatteryHeaderPreferenceController.quickUpdateHeaderPreference();
}
}
@Override
@@ -392,7 +404,6 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList
restartBatteryTipLoader();
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.power_usage_summary);
}

View File

@@ -18,6 +18,7 @@ package com.android.settings.fuelgauge;
import android.content.Context;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
@@ -30,6 +31,8 @@ import com.android.settingslib.core.lifecycle.events.OnStop;
public class TopLevelBatteryPreferenceController extends BasePreferenceController implements
LifecycleObserver, OnStart, OnStop {
@VisibleForTesting
boolean mIsBatteryPresent = true;
private final BatteryBroadcastReceiver mBatteryBroadcastReceiver;
private Preference mPreference;
private BatteryInfo mBatteryInfo;
@@ -38,6 +41,9 @@ public class TopLevelBatteryPreferenceController extends BasePreferenceControlle
super(context, preferenceKey);
mBatteryBroadcastReceiver = new BatteryBroadcastReceiver(mContext);
mBatteryBroadcastReceiver.setBatteryChangedListener(type -> {
if (type == BatteryBroadcastReceiver.BatteryUpdateType.BATTERY_NOT_PRESENT) {
mIsBatteryPresent = false;
}
BatteryInfo.getBatteryInfo(mContext, info -> {
mBatteryInfo = info;
updateState(mPreference);
@@ -69,6 +75,10 @@ public class TopLevelBatteryPreferenceController extends BasePreferenceControlle
@Override
public CharSequence getSummary() {
// Display help message if battery is not present.
if (!mIsBatteryPresent) {
return mContext.getText(R.string.battery_missing_message);
}
return getDashboardLabel(mContext, mBatteryInfo);
}

View File

@@ -23,6 +23,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.os.BatteryStatsHelper;
import com.android.settings.fuelgauge.BatteryInfo;
import com.android.settings.fuelgauge.BatteryUtils;
import com.android.settings.fuelgauge.batterytip.detectors.BatteryDefenderDetector;
import com.android.settings.fuelgauge.batterytip.detectors.EarlyWarningDetector;
import com.android.settings.fuelgauge.batterytip.detectors.HighUsageDetector;
import com.android.settings.fuelgauge.batterytip.detectors.LowBatteryDetector;
@@ -72,6 +73,7 @@ public class BatteryTipLoader extends AsyncLoaderCompat<List<BatteryTip>> {
batteryInfo.discharging).detect());
tips.add(new SmartBatteryDetector(policy, context.getContentResolver()).detect());
tips.add(new EarlyWarningDetector(policy, context).detect());
tips.add(new BatteryDefenderDetector(batteryInfo).detect());
tips.add(new SummaryDetector(policy, batteryInfo.averageTimeToDischarge).detect());
// Disable this feature now since it introduces false positive cases. We will try to improve
// it in the future.

View File

@@ -29,6 +29,7 @@ import androidx.annotation.NonNull;
import com.android.internal.util.CollectionUtils;
import com.android.settings.SettingsActivity;
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.fuelgauge.batterytip.actions.BatteryDefenderAction;
import com.android.settings.fuelgauge.batterytip.actions.BatterySaverAction;
import com.android.settings.fuelgauge.batterytip.actions.BatteryTipAction;
import com.android.settings.fuelgauge.batterytip.actions.OpenBatterySaverAction;
@@ -111,6 +112,8 @@ public class BatteryTipUtils {
}
case BatteryTip.TipType.REMOVE_APP_RESTRICTION:
return new UnrestrictAppAction(settingsActivity, (UnrestrictAppTip) batteryTip);
case BatteryTip.TipType.BATTERY_DEFENDER:
return new BatteryDefenderAction(settingsActivity);
default:
return null;
}

View File

@@ -0,0 +1,50 @@
/*
* 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.settings.fuelgauge.batterytip.actions;
import android.content.Intent;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settingslib.HelpUtils;
/**
* Action to open the Support Center article
*/
public class BatteryDefenderAction extends BatteryTipAction {
private SettingsActivity mSettingsActivity;
public BatteryDefenderAction(SettingsActivity settingsActivity) {
super(settingsActivity.getApplicationContext());
mSettingsActivity = settingsActivity;
}
/**
* Handle the action when user clicks positive button
*/
@Override
public void handlePositiveAction(int metricsKey) {
final Intent intent = HelpUtils.getHelpIntent(
mContext,
mContext.getString(R.string.help_url_battery_defender),
getClass().getName());
if (intent != null) {
mSettingsActivity.startActivityForResult(intent, 0);
}
// TODO(b/173985153): Add logging enums for Battery Defender.
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.settings.fuelgauge.batterytip.detectors;
import com.android.settings.fuelgauge.BatteryInfo;
import com.android.settings.fuelgauge.BatteryUtils;
import com.android.settings.fuelgauge.batterytip.tips.BatteryDefenderTip;
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
/**
* Detect whether the battery is overheated
*/
public class BatteryDefenderDetector implements BatteryTipDetector {
private BatteryInfo mBatteryInfo;
public BatteryDefenderDetector(BatteryInfo batteryInfo) {
mBatteryInfo = batteryInfo;
}
@Override
public BatteryTip detect() {
final int state =
BatteryUtils.isBatteryDefenderOn(mBatteryInfo)
? BatteryTip.StateType.NEW
: BatteryTip.StateType.INVISIBLE;
return new BatteryDefenderTip(state);
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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.settings.fuelgauge.batterytip.tips;
import android.content.Context;
import android.os.Parcel;
import com.android.settings.R;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
/**
* Tip to show current battery is overheated
*/
public class BatteryDefenderTip extends BatteryTip {
public BatteryDefenderTip(@StateType int state) {
super(TipType.BATTERY_DEFENDER, state, false /* showDialog */);
}
private BatteryDefenderTip(Parcel in) {
super(in);
}
@Override
public CharSequence getTitle(Context context) {
return context.getString(R.string.battery_tip_limited_temporarily_title);
}
@Override
public CharSequence getSummary(Context context) {
return context.getString(R.string.battery_tip_limited_temporarily_summary);
}
@Override
public int getIconId() {
return R.drawable.ic_battery_status_good_24dp;
}
@Override
public void updateState(BatteryTip tip) {
mState = tip.mState;
}
@Override
public void log(Context context, MetricsFeatureProvider metricsFeatureProvider) {
// TODO(b/173985153): Add logging enums for Battery Defender.
}
public static final Creator CREATOR = new Creator() {
public BatteryTip createFromParcel(Parcel in) {
return new BatteryDefenderTip(in);
}
public BatteryTip[] newArray(int size) {
return new BatteryDefenderTip[size];
}
};
}

View File

@@ -17,7 +17,6 @@
package com.android.settings.fuelgauge.batterytip.tips;
import android.content.Context;
import android.content.res.ColorStateList;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.SparseIntArray;
@@ -58,7 +57,8 @@ public abstract class BatteryTip implements Comparable<BatteryTip>, Parcelable {
TipType.APP_RESTRICTION,
TipType.REDUCED_BATTERY,
TipType.LOW_BATTERY,
TipType.REMOVE_APP_RESTRICTION})
TipType.REMOVE_APP_RESTRICTION,
TipType.BATTERY_DEFENDER})
public @interface TipType {
int SMART_BATTERY_MANAGER = 0;
int APP_RESTRICTION = 1;
@@ -68,20 +68,22 @@ public abstract class BatteryTip implements Comparable<BatteryTip>, Parcelable {
int LOW_BATTERY = 5;
int SUMMARY = 6;
int REMOVE_APP_RESTRICTION = 7;
int BATTERY_DEFENDER = 8;
}
@VisibleForTesting
static final SparseIntArray TIP_ORDER;
static {
TIP_ORDER = new SparseIntArray();
TIP_ORDER.append(TipType.APP_RESTRICTION, 0);
TIP_ORDER.append(TipType.BATTERY_SAVER, 1);
TIP_ORDER.append(TipType.HIGH_DEVICE_USAGE, 2);
TIP_ORDER.append(TipType.LOW_BATTERY, 3);
TIP_ORDER.append(TipType.SUMMARY, 4);
TIP_ORDER.append(TipType.SMART_BATTERY_MANAGER, 5);
TIP_ORDER.append(TipType.REDUCED_BATTERY, 6);
TIP_ORDER.append(TipType.REMOVE_APP_RESTRICTION, 7);
TIP_ORDER.append(TipType.BATTERY_DEFENDER, 0);
TIP_ORDER.append(TipType.APP_RESTRICTION, 1);
TIP_ORDER.append(TipType.BATTERY_SAVER, 2);
TIP_ORDER.append(TipType.HIGH_DEVICE_USAGE, 3);
TIP_ORDER.append(TipType.LOW_BATTERY, 4);
TIP_ORDER.append(TipType.SUMMARY, 5);
TIP_ORDER.append(TipType.SMART_BATTERY_MANAGER, 6);
TIP_ORDER.append(TipType.REDUCED_BATTERY, 7);
TIP_ORDER.append(TipType.REMOVE_APP_RESTRICTION, 8);
}
private static final String KEY_PREFIX = "key_battery_tip";

View File

@@ -0,0 +1,46 @@
/*
* Copyright 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.settings.media;
import android.app.settings.SettingsEnums;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import com.android.settings.R;
import com.android.settings.bluetooth.BluetoothPairingDetail;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.media.MediaOutputSliceConstants;
/**
* BroadcastReceiver for handling media output intent
*/
public class BluetoothPairingReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (TextUtils.equals(MediaOutputSliceConstants.ACTION_LAUNCH_BLUETOOTH_PAIRING,
intent.getAction())) {
context.startActivity(new SubSettingLauncher(context)
.setDestination(BluetoothPairingDetail.class.getName())
.setTitleRes(R.string.bluetooth_pairing_page_title)
.setSourceMetricsCategory(SettingsEnums.BLUETOOTH_PAIRING_RECEIVER)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
.toIntent());
}
}
}

View File

@@ -30,6 +30,7 @@ import android.net.Uri;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
@@ -51,6 +52,9 @@ import java.util.concurrent.CopyOnWriteArrayList;
public class MediaDeviceUpdateWorker extends SliceBackgroundWorker
implements LocalMediaManager.DeviceCallback {
private static final String TAG = "MediaDeviceUpdateWorker";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
protected final Context mContext;
protected final Collection<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
private final DevicesChangedBroadcastReceiver mReceiver;
@@ -213,6 +217,10 @@ public class MediaDeviceUpdateWorker extends SliceBackgroundWorker
final List<RoutingSessionInfo> sessionInfos = new ArrayList<>();
for (RoutingSessionInfo info : mLocalMediaManager.getActiveMediaSession()) {
if (!info.isSystemSession()) {
if (DEBUG) {
Log.d(TAG, "getActiveRemoteMediaDevice() info : " + info.toString()
+ ", package name : " + info.getClientPackageName());
}
sessionInfos.add(info);
}
}

View File

@@ -16,18 +16,15 @@
package com.android.settings.media;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI;
import android.annotation.ColorInt;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.media.session.MediaController;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.core.graphics.drawable.IconCompat;
@@ -63,14 +60,9 @@ public class MediaOutputIndicatorSlice implements CustomSliceable {
com.android.internal.R.drawable.ic_settings_bluetooth);
final CharSequence title = mContext.getString(R.string.media_output_label_title,
Utils.getApplicationLabel(mContext, getWorker().getPackageName()));
final int requestCode = TextUtils.isEmpty(getWorker().getPackageName())
? 0
: getWorker().getPackageName().hashCode();
final PendingIntent primaryActionIntent = PendingIntent.getActivity(mContext,
requestCode,
getMediaOutputSliceIntent(), FLAG_UPDATE_CURRENT);
final SliceAction primarySliceAction = SliceAction.createDeeplink(
primaryActionIntent, icon, ListBuilder.ICON_IMAGE, title);
final SliceAction primarySliceAction = SliceAction.create(
getBroadcastIntent(mContext), icon, ListBuilder.ICON_IMAGE, title);
@ColorInt final int color = Utils.getColorAccentDefaultColor(mContext);
// To set an empty icon to indent the row
final ListBuilder listBuilder = new ListBuilder(mContext, getUri(), ListBuilder.INFINITY)
@@ -83,22 +75,6 @@ public class MediaOutputIndicatorSlice implements CustomSliceable {
return listBuilder.build();
}
@VisibleForTesting
Intent getMediaOutputSliceIntent() {
final MediaController mediaController = getWorker().getActiveLocalMediaController();
final Intent intent = new Intent()
.setPackage(Utils.SETTINGS_PACKAGE_NAME)
.setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (mediaController != null) {
intent.putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN,
mediaController.getSessionToken());
intent.putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
mediaController.getPackageName());
}
return intent;
}
private IconCompat createEmptyIcon() {
final Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
return IconCompat.createWithBitmap(bitmap);
@@ -141,4 +117,26 @@ public class MediaOutputIndicatorSlice implements CustomSliceable {
&& getWorker().getMediaDevices().size() > 0
&& getWorker().getActiveLocalMediaController() != null;
}
@Override
public void onNotifyChange(Intent intent) {
final MediaController mediaController = getWorker().getActiveLocalMediaController();
if (mediaController == null) {
Log.d(TAG, "No active local media controller");
return;
}
// Launch media output dialog
mContext.sendBroadcast(new Intent()
.setPackage(MediaOutputSliceConstants.SYSTEMUI_PACKAGE_NAME)
.setAction(MediaOutputSliceConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG)
.putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN,
mediaController.getSessionToken())
.putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
mediaController.getPackageName()));
// Dismiss volume panel
mContext.sendBroadcast(new Intent()
.setPackage(MediaOutputSliceConstants.SETTINGS_PACKAGE_NAME)
.setAction(MediaOutputSliceConstants.ACTION_CLOSE_PANEL));
}
}

View File

@@ -25,7 +25,6 @@ import android.content.IntentFilter;
import android.media.AudioManager;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
@@ -53,6 +52,7 @@ public class MediaOutputIndicatorWorker extends SliceBackgroundWorker implements
LocalMediaManager.DeviceCallback {
private static final String TAG = "MediaOutputIndWorker";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final DevicesChangedBroadcastReceiver mReceiver;
private final Context mContext;
@@ -127,24 +127,8 @@ public class MediaOutputIndicatorWorker extends SliceBackgroundWorker implements
@Nullable
MediaController getActiveLocalMediaController() {
final MediaSessionManager mMediaSessionManager = mContext.getSystemService(
MediaSessionManager.class);
for (MediaController controller : mMediaSessionManager.getActiveSessions(null)) {
final MediaController.PlaybackInfo pi = controller.getPlaybackInfo();
if (pi == null) {
return null;
}
final PlaybackState playbackState = controller.getPlaybackState();
if (playbackState == null) {
return null;
}
if (pi.getPlaybackType() == MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL
&& playbackState.getState() == PlaybackState.STATE_PLAYING) {
return controller;
}
}
return null;
return MediaOutputUtils.getActiveLocalMediaController(mContext.getSystemService(
MediaSessionManager.class));
}
@Override

View File

@@ -396,7 +396,7 @@ public class MediaOutputSlice implements CustomSliceable {
@Override
public Class getBackgroundWorkerClass() {
return MediaOutputSliceWorker.class;
return MediaDeviceUpdateWorker.class;
}
private boolean isVisible() {

View File

@@ -1,211 +0,0 @@
/*
* 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.settings.media;
import static android.media.MediaRoute2ProviderService.REASON_INVALID_COMMAND;
import static android.media.MediaRoute2ProviderService.REASON_NETWORK_ERROR;
import static android.media.MediaRoute2ProviderService.REASON_REJECTED;
import static android.media.MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE;
import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.net.Uri;
import android.util.Log;
import com.android.settings.core.instrumentation.SettingsStatsLog;
import com.android.settingslib.media.MediaDevice;
/**
* SliceBackgroundWorker for the MediaOutputSlice class.
* It inherits from MediaDeviceUpdateWorker and add metrics logging.
*/
public class MediaOutputSliceWorker extends MediaDeviceUpdateWorker {
private static final String TAG = "MediaOutputSliceWorker";
private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
private MediaDevice mSourceDevice, mTargetDevice;
private int mWiredDeviceCount;
private int mConnectedBluetoothDeviceCount;
private int mRemoteDeviceCount;
private int mAppliedDeviceCountWithinRemoteGroup;
public MediaOutputSliceWorker(Context context, Uri uri) {
super(context, uri);
}
@Override
public void connectDevice(MediaDevice device) {
mSourceDevice = mLocalMediaManager.getCurrentConnectedDevice();
mTargetDevice = device;
if (DBG) {
Log.d(TAG, "connectDevice -"
+ " source:" + mSourceDevice.toString()
+ " target:" + mTargetDevice.toString());
}
super.connectDevice(device);
}
private int getLoggingDeviceType(MediaDevice device, boolean isSourceDevice) {
switch (device.getDeviceType()) {
case MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE:
return isSourceDevice
? SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__BUILTIN_SPEAKER
: SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__BUILTIN_SPEAKER;
case MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE:
return isSourceDevice
? SettingsStatsLog
.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__WIRED_3POINT5_MM_AUDIO
: SettingsStatsLog
.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__WIRED_3POINT5_MM_AUDIO;
case MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE:
return isSourceDevice
? SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__USB_C_AUDIO
: SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__USB_C_AUDIO;
case MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE:
return isSourceDevice
? SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__BLUETOOTH
: SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__BLUETOOTH;
case MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE:
return isSourceDevice
? SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__REMOTE_SINGLE
: SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__REMOTE_SINGLE;
case MediaDevice.MediaDeviceType.TYPE_CAST_GROUP_DEVICE:
return isSourceDevice
? SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__REMOTE_GROUP
: SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__REMOTE_GROUP;
default:
return isSourceDevice
? SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__UNKNOWN_TYPE
: SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__UNKNOWN_TYPE;
}
}
private int getLoggingSwitchOpSubResult(int reason) {
switch (reason) {
case REASON_REJECTED:
return SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__REJECTED;
case REASON_NETWORK_ERROR:
return SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__NETWORK_ERROR;
case REASON_ROUTE_NOT_AVAILABLE:
return SettingsStatsLog
.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__ROUTE_NOT_AVAILABLE;
case REASON_INVALID_COMMAND:
return SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__INVALID_COMMAND;
case REASON_UNKNOWN_ERROR:
default:
return SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__UNKNOWN_ERROR;
}
}
private String getLoggingPackageName() {
final String packageName = getPackageName();
if (packageName != null && !packageName.isEmpty()) {
try {
final ApplicationInfo applicationInfo = mContext.getPackageManager()
.getApplicationInfo(packageName, /* default flag */ 0);
if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
|| (applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
return packageName;
}
} catch (Exception ex) {
Log.e(TAG, packageName + "is invalid.");
}
}
return "";
}
private void updateLoggingDeviceCount() {
mWiredDeviceCount = mConnectedBluetoothDeviceCount = mRemoteDeviceCount = 0;
mAppliedDeviceCountWithinRemoteGroup = 0;
for (MediaDevice mediaDevice : mMediaDevices) {
if (mediaDevice.isConnected()) {
switch (mediaDevice.getDeviceType()) {
case MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE:
case MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE:
mWiredDeviceCount++;
break;
case MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE:
mConnectedBluetoothDeviceCount++;
break;
case MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE:
case MediaDevice.MediaDeviceType.TYPE_CAST_GROUP_DEVICE:
mRemoteDeviceCount++;
break;
default:
}
}
}
if (DBG) {
Log.d(TAG, "connected devices:" + " wired: " + mWiredDeviceCount
+ " bluetooth: " + mConnectedBluetoothDeviceCount
+ " remote: " + mRemoteDeviceCount);
}
}
@Override
public void onSelectedDeviceStateChanged(MediaDevice device, int state) {
if (DBG) {
Log.d(TAG, "onSelectedDeviceStateChanged - " + device.toString());
}
updateLoggingDeviceCount();
SettingsStatsLog.write(
SettingsStatsLog.MEDIAOUTPUT_OP_SWITCH_REPORTED,
getLoggingDeviceType(mSourceDevice, true),
getLoggingDeviceType(mTargetDevice, false),
SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__RESULT__OK,
SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__NO_ERROR,
getLoggingPackageName(),
mWiredDeviceCount,
mConnectedBluetoothDeviceCount,
mRemoteDeviceCount,
mAppliedDeviceCountWithinRemoteGroup);
super.onSelectedDeviceStateChanged(device, state);
}
@Override
public void onRequestFailed(int reason) {
if (DBG) {
Log.e(TAG, "onRequestFailed - " + reason);
}
updateLoggingDeviceCount();
SettingsStatsLog.write(
SettingsStatsLog.MEDIAOUTPUT_OP_SWITCH_REPORTED,
getLoggingDeviceType(mSourceDevice, true),
getLoggingDeviceType(mTargetDevice, false),
SettingsStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__RESULT__ERROR,
getLoggingSwitchOpSubResult(reason),
getLoggingPackageName(),
mWiredDeviceCount,
mConnectedBluetoothDeviceCount,
mRemoteDeviceCount,
mAppliedDeviceCountWithinRemoteGroup);
super.onRequestFailed(reason);
}
}

View File

@@ -0,0 +1,90 @@
/*
* 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.settings.media;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.Nullable;
import com.android.settings.sound.MediaOutputPreferenceController;
import java.util.ArrayList;
import java.util.List;
/**
* Utilities that can be shared between {@link MediaOutputIndicatorWorker} and
* {@link MediaOutputPreferenceController}.
*/
public class MediaOutputUtils {
private static final String TAG = "MediaOutputUtils";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
/**
* Returns a {@link MediaController} that state is playing and type is local playback,
* and also have active sessions.
*/
@Nullable
public static MediaController getActiveLocalMediaController(
MediaSessionManager mediaSessionManager) {
MediaController localController = null;
final List<String> remoteMediaSessionLists = new ArrayList<>();
for (MediaController controller : mediaSessionManager.getActiveSessions(null)) {
final MediaController.PlaybackInfo pi = controller.getPlaybackInfo();
if (pi == null) {
// do nothing
continue;
}
final PlaybackState playbackState = controller.getPlaybackState();
if (playbackState == null) {
// do nothing
continue;
}
if (DEBUG) {
Log.d(TAG, "getActiveLocalMediaController() package name : "
+ controller.getPackageName()
+ ", play back type : " + pi.getPlaybackType() + ", play back state : "
+ playbackState.getState());
}
if (playbackState.getState() != PlaybackState.STATE_PLAYING) {
// do nothing
continue;
}
if (pi.getPlaybackType() == MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
if (localController != null && TextUtils.equals(localController.getPackageName(),
controller.getPackageName())) {
localController = null;
}
if (!remoteMediaSessionLists.contains(controller.getPackageName())) {
remoteMediaSessionLists.add(controller.getPackageName());
}
continue;
}
if (pi.getPlaybackType() == MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
if (localController == null
&& !remoteMediaSessionLists.contains(controller.getPackageName())) {
localController = controller;
}
}
}
return localController;
}
}

View File

@@ -59,6 +59,9 @@ public class RemoteMediaSlice implements CustomSliceable {
private static final String TAG = "RemoteMediaSlice";
private static final String MEDIA_ID = "media_id";
private static final String ACTION_LAUNCH_DIALOG = "action_launch_dialog";
private static final String SESSION_INFO = "RoutingSessionInfo";
private static final String CUSTOMIZED_ACTION = "customized_action";
private final Context mContext;
@@ -77,6 +80,20 @@ public class RemoteMediaSlice implements CustomSliceable {
final String id = intent.getStringExtra(MEDIA_ID);
if (!TextUtils.isEmpty(id)) {
getWorker().adjustSessionVolume(id, newPosition);
return;
}
if (TextUtils.equals(ACTION_LAUNCH_DIALOG, intent.getStringExtra(CUSTOMIZED_ACTION))) {
// Launch Media Output Dialog
final RoutingSessionInfo info = intent.getParcelableExtra(SESSION_INFO);
mContext.sendBroadcast(new Intent()
.setPackage(MediaOutputSliceConstants.SYSTEMUI_PACKAGE_NAME)
.setAction(MediaOutputSliceConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG)
.putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
info.getClientPackageName()));
// Dismiss volume panel
mContext.sendBroadcast(new Intent()
.setPackage(MediaOutputSliceConstants.SETTINGS_PACKAGE_NAME)
.setAction(MediaOutputSliceConstants.ACTION_CLOSE_PANEL));
}
}
@@ -133,8 +150,7 @@ public class RemoteMediaSlice implements CustomSliceable {
.setTitle(isMediaOutputDisabled ? spannableTitle : outputTitle)
.setSubtitle(info.getName())
.setTitleItem(emptyIcon, ListBuilder.ICON_IMAGE)
.setPrimaryAction(getMediaOutputSliceAction(
info.getClientPackageName(), isMediaOutputDisabled)));
.setPrimaryAction(getMediaOutputDialogAction(info, isMediaOutputDisabled)));
}
return listBuilder.build();
}
@@ -161,29 +177,30 @@ public class RemoteMediaSlice implements CustomSliceable {
mContext.getText(R.string.sound_settings).toString(), 0);
intent.setClassName(mContext.getPackageName(), SubSettings.class.getName());
intent.setData(contentUri);
final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent,
PendingIntent.FLAG_IMMUTABLE);
final SliceAction primarySliceAction = SliceAction.createDeeplink(pendingIntent, icon,
ListBuilder.ICON_IMAGE, actionTitle);
return primarySliceAction;
}
private SliceAction getMediaOutputSliceAction(
String packageName, boolean isMediaOutputDisabled) {
final Intent intent = new Intent()
.setAction(isMediaOutputDisabled
? ""
: MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME, packageName);
final IconCompat icon = IconCompat.createWithResource(mContext,
R.drawable.ic_volume_remote);
final int requestCode = TextUtils.isEmpty(packageName) ? 0 : packageName.hashCode();
final PendingIntent primaryActionIntent = PendingIntent.getActivity(mContext,
requestCode, intent, 0 /* flags */);
private SliceAction getMediaOutputDialogAction(RoutingSessionInfo info,
boolean isMediaOutputDisabled) {
final Intent intent = new Intent(getUri().toString())
.setData(getUri())
.setClass(mContext, SliceBroadcastReceiver.class)
.putExtra(CUSTOMIZED_ACTION, isMediaOutputDisabled ? "" : ACTION_LAUNCH_DIALOG)
.putExtra(SESSION_INFO, info)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
final PendingIntent primaryBroadcastIntent = PendingIntent.getBroadcast(mContext,
info.hashCode(), intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
final SliceAction primarySliceAction = SliceAction.createDeeplink(
primaryActionIntent, icon, ListBuilder.ICON_IMAGE,
primaryBroadcastIntent,
IconCompat.createWithResource(mContext, R.drawable.ic_volume_remote),
ListBuilder.ICON_IMAGE,
mContext.getString(R.string.media_output_label_title,
Utils.getApplicationLabel(mContext, packageName)));
Utils.getApplicationLabel(mContext, info.getClientPackageName())));
return primarySliceAction;
}

View File

@@ -52,6 +52,7 @@ import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.net.SignalStrengthUtil;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -166,6 +167,12 @@ public class SubscriptionsPreferenceController extends AbstractPreferenceControl
final int dataDefaultSubId = SubscriptionManager.getDefaultDataSubscriptionId();
for (SubscriptionInfo info : SubscriptionUtil.getActiveSubscriptions(mManager)) {
final int subId = info.getSubscriptionId();
// Avoid from showing subscription(SIM)s which has been marked as hidden
// For example, only one subscription will be shown when there're multiple
// subscriptions with same group UUID.
if (!canSubscriptionBeDisplayed(mContext, subId)) {
continue;
}
activeSubIds.add(subId);
Preference pref = existingPrefs.remove(subId);
if (pref == null) {
@@ -292,7 +299,17 @@ public class SubscriptionsPreferenceController extends AbstractPreferenceControl
if (mSubscriptionsListener.isAirplaneModeOn()) {
return false;
}
return SubscriptionUtil.getActiveSubscriptions(mManager).size() >= 2;
List<SubscriptionInfo> subInfoList = SubscriptionUtil.getActiveSubscriptions(mManager);
if (subInfoList == null) {
return false;
}
return subInfoList.stream()
// Avoid from showing subscription(SIM)s which has been marked as hidden
// For example, only one subscription will be shown when there're multiple
// subscriptions with same group UUID.
.filter(subInfo ->
canSubscriptionBeDisplayed(mContext, subInfo.getSubscriptionId()))
.count() >= 2;
}
@Override
@@ -330,4 +347,10 @@ public class SubscriptionsPreferenceController extends AbstractPreferenceControl
public void onSignalStrengthChanged() {
update();
}
@VisibleForTesting
boolean canSubscriptionBeDisplayed(Context context, int subId) {
return (SubscriptionUtil.getAvailableSubscription(context,
ProxySubscriptionManager.getInstance(context), subId) != null);
}
}

View File

@@ -102,43 +102,80 @@ public class RemoteVolumeGroupController extends BasePreferenceController implem
mLocalMediaManager.stopScan();
}
private void refreshPreference() {
mPreferenceCategory.removeAll();
private synchronized void refreshPreference() {
if (!isAvailable()) {
mPreferenceCategory.setVisible(false);
return;
}
final CharSequence castVolume = mContext.getText(R.string.remote_media_volume_option_title);
mPreferenceCategory.setVisible(true);
for (RoutingSessionInfo info : mRoutingSessionInfos) {
if (mPreferenceCategory.findPreference(info.getId()) != null) {
continue;
final CharSequence appName = Utils.getApplicationLabel(mContext,
info.getClientPackageName());
RemoteVolumeSeekBarPreference seekBarPreference = mPreferenceCategory.findPreference(
info.getId());
if (seekBarPreference != null) {
// Update slider
if (seekBarPreference.getProgress() != info.getVolume()) {
seekBarPreference.setProgress(info.getVolume());
}
} else {
// Add slider
seekBarPreference = new RemoteVolumeSeekBarPreference(mContext);
seekBarPreference.setKey(info.getId());
seekBarPreference.setTitle(castVolume);
seekBarPreference.setMax(info.getVolumeMax());
seekBarPreference.setProgress(info.getVolume());
seekBarPreference.setMin(0);
seekBarPreference.setOnPreferenceChangeListener(this);
seekBarPreference.setIcon(R.drawable.ic_volume_remote);
mPreferenceCategory.addPreference(seekBarPreference);
}
final CharSequence appName = Utils.getApplicationLabel(
mContext, info.getClientPackageName());
final CharSequence outputTitle = mContext.getString(R.string.media_output_label_title,
appName);
// Add slider
final RemoteVolumeSeekBarPreference seekBarPreference =
new RemoteVolumeSeekBarPreference(mContext);
seekBarPreference.setKey(info.getId());
seekBarPreference.setTitle(castVolume);
seekBarPreference.setMax(info.getVolumeMax());
seekBarPreference.setProgress(info.getVolume());
seekBarPreference.setMin(0);
seekBarPreference.setOnPreferenceChangeListener(this);
seekBarPreference.setIcon(R.drawable.ic_volume_remote);
mPreferenceCategory.addPreference(seekBarPreference);
// Add output indicator
Preference switcherPreference = mPreferenceCategory.findPreference(
SWITCHER_PREFIX + info.getId());
final boolean isMediaOutputDisabled = Utils.isMediaOutputDisabled(
mRouterManager, info.getClientPackageName());
final Preference preference = new Preference(mContext);
preference.setKey(SWITCHER_PREFIX + info.getId());
preference.setTitle(isMediaOutputDisabled ? appName : outputTitle);
preference.setSummary(info.getName());
preference.setEnabled(!isMediaOutputDisabled);
mPreferenceCategory.addPreference(preference);
final CharSequence outputTitle = mContext.getString(R.string.media_output_label_title,
appName);
if (switcherPreference != null) {
// Update output indicator
switcherPreference.setTitle(isMediaOutputDisabled ? appName : outputTitle);
switcherPreference.setSummary(info.getName());
switcherPreference.setEnabled(!isMediaOutputDisabled);
} else {
// Add output indicator
switcherPreference = new Preference(mContext);
switcherPreference.setKey(SWITCHER_PREFIX + info.getId());
switcherPreference.setTitle(isMediaOutputDisabled ? appName : outputTitle);
switcherPreference.setSummary(info.getName());
switcherPreference.setEnabled(!isMediaOutputDisabled);
mPreferenceCategory.addPreference(switcherPreference);
}
}
// Check and remove non-active session preference
// There is a pair of preferences for each session. First one is a seekBar preference.
// The second one shows the session information and provide an entry-point to launch output
// switcher. It is unnecessary to go through all preferences. It is fine ignore the second
// preference and only to check the seekBar's key value.
for (int i = 0; i < mPreferenceCategory.getPreferenceCount(); i = i + 2) {
final Preference preference = mPreferenceCategory.getPreference(i);
boolean isActive = false;
for (RoutingSessionInfo info : mRoutingSessionInfos) {
if (TextUtils.equals(preference.getKey(), info.getId())) {
isActive = true;
break;
}
}
if (isActive) {
continue;
}
final Preference switcherPreference = mPreferenceCategory.getPreference(i + 1);
if (switcherPreference != null) {
mPreferenceCategory.removePreference(preference);
mPreferenceCategory.removePreference(switcherPreference);
}
}
}
@@ -159,11 +196,11 @@ public class RemoteVolumeGroupController extends BasePreferenceController implem
if (TextUtils.equals(info.getId(),
preference.getKey().substring(SWITCHER_PREFIX.length()))) {
final Intent intent = new Intent()
.setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setAction(MediaOutputSliceConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG)
.setPackage(MediaOutputSliceConstants.SYSTEMUI_PACKAGE_NAME)
.putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
info.getClientPackageName());
mContext.startActivity(intent);
mContext.sendBroadcast(intent);
return true;
}
}
@@ -181,8 +218,10 @@ public class RemoteVolumeGroupController extends BasePreferenceController implem
// Preference group is not ready.
return;
}
initRemoteMediaSession();
refreshPreference();
ThreadUtils.postOnMainThread(() -> {
initRemoteMediaSession();
refreshPreference();
});
}
@Override

View File

@@ -23,16 +23,14 @@ import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.graphics.BlendMode;
import android.graphics.BlendModeColorFilter;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceGroup;
@@ -53,7 +51,8 @@ import java.util.List;
public class ChannelListPreferenceController extends NotificationPreferenceController {
private static final String KEY = "channels";
private static String KEY_GENERAL_CATEGORY = "categories";
private static final String KEY_GENERAL_CATEGORY = "categories";
private static final String KEY_ZERO_CATEGORIES = "zeroCategories";
public static final String ARG_FROM_SETTINGS = "fromSettings";
private List<NotificationChannelGroup> mChannelGroupList;
@@ -102,62 +101,192 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
if (mContext == null) {
return;
}
populateList();
updateFullList(mPreference, mChannelGroupList);
}
}.execute();
}
private void populateList() {
// TODO: if preference has children, compare with newly loaded list
mPreference.removeAll();
if (mChannelGroupList.isEmpty()) {
PreferenceCategory groupCategory = new PreferenceCategory(mContext);
groupCategory.setTitle(R.string.notification_channels);
groupCategory.setKey(KEY_GENERAL_CATEGORY);
mPreference.addPreference(groupCategory);
Preference empty = new Preference(mContext);
empty.setTitle(R.string.no_channels);
empty.setEnabled(false);
groupCategory.addPreference(empty);
} else {
populateGroupList();
}
}
private void populateGroupList() {
for (NotificationChannelGroup group : mChannelGroupList) {
PreferenceCategory groupCategory = new PreferenceCategory(mContext);
groupCategory.setOrderingAsAdded(true);
mPreference.addPreference(groupCategory);
if (group.getId() == null) {
groupCategory.setTitle(R.string.notification_channels_other);
groupCategory.setKey(KEY_GENERAL_CATEGORY);
/**
* Update the preferences group to match the
* @param groupPrefsList
* @param channelGroups
*/
void updateFullList(@NonNull PreferenceCategory groupPrefsList,
@NonNull List<NotificationChannelGroup> channelGroups) {
if (channelGroups.isEmpty()) {
if (groupPrefsList.getPreferenceCount() == 1
&& KEY_ZERO_CATEGORIES.equals(groupPrefsList.getPreference(0).getKey())) {
// Ensure the titles are correct for the current language, but otherwise leave alone
PreferenceGroup groupCategory = (PreferenceGroup) groupPrefsList.getPreference(0);
groupCategory.setTitle(R.string.notification_channels);
groupCategory.getPreference(0).setTitle(R.string.no_channels);
} else {
groupCategory.setTitle(group.getName());
groupCategory.setKey(group.getId());
populateGroupToggle(groupCategory, group);
// Clear any contents and create the 'zero-categories' group.
groupPrefsList.removeAll();
PreferenceCategory groupCategory = new PreferenceCategory(mContext);
groupCategory.setTitle(R.string.notification_channels);
groupCategory.setKey(KEY_ZERO_CATEGORIES);
groupPrefsList.addPreference(groupCategory);
Preference empty = new Preference(mContext);
empty.setTitle(R.string.no_channels);
empty.setEnabled(false);
groupCategory.addPreference(empty);
}
if (!group.isBlocked()) {
final List<NotificationChannel> channels = group.getChannels();
Collections.sort(channels, CHANNEL_COMPARATOR);
int N = channels.size();
for (int i = 0; i < N; i++) {
final NotificationChannel channel = channels.get(i);
// conversations get their own section
if (TextUtils.isEmpty(channel.getConversationId()) || channel.isDemoted()) {
populateSingleChannelPrefs(groupCategory, channel, group.isBlocked());
}
}
} else {
updateGroupList(groupPrefsList, channelGroups);
}
}
/**
* Looks for the category for the given group's key at the expected index, if that doesn't
* match, it checks all groups, and if it can't find that group anywhere, it creates it.
*/
@NonNull
private PreferenceCategory findOrCreateGroupCategoryForKey(
@NonNull PreferenceCategory groupPrefsList, @Nullable String key, int expectedIndex) {
if (key == null) {
key = KEY_GENERAL_CATEGORY;
}
int preferenceCount = groupPrefsList.getPreferenceCount();
if (expectedIndex < preferenceCount) {
Preference preference = groupPrefsList.getPreference(expectedIndex);
if (key.equals(preference.getKey())) {
return (PreferenceCategory) preference;
}
}
for (int i = 0; i < preferenceCount; i++) {
Preference preference = groupPrefsList.getPreference(i);
if (key.equals(preference.getKey())) {
preference.setOrder(expectedIndex);
return (PreferenceCategory) preference;
}
}
PreferenceCategory groupCategory = new PreferenceCategory(mContext);
groupCategory.setOrder(expectedIndex);
groupCategory.setKey(key);
groupPrefsList.addPreference(groupCategory);
return groupCategory;
}
private void updateGroupList(@NonNull PreferenceCategory groupPrefsList,
@NonNull List<NotificationChannelGroup> channelGroups) {
// Update the list, but optimize for the most common case where the list hasn't changed.
int numFinalGroups = channelGroups.size();
int initialPrefCount = groupPrefsList.getPreferenceCount();
List<PreferenceCategory> finalOrderedGroups = new ArrayList<>(numFinalGroups);
for (int i = 0; i < numFinalGroups; i++) {
NotificationChannelGroup group = channelGroups.get(i);
PreferenceCategory groupCategory =
findOrCreateGroupCategoryForKey(groupPrefsList, group.getId(), i);
finalOrderedGroups.add(groupCategory);
updateGroupPreferences(group, groupCategory);
}
int postAddPrefCount = groupPrefsList.getPreferenceCount();
// If any groups were inserted (into a non-empty list) or need to be removed, we need to
// remove all groups and re-add them all.
// This is required to ensure proper ordering of inserted groups, and it simplifies logic
// at the cost of computation in the rare case that the list is changing.
boolean hasInsertions = initialPrefCount != 0 && initialPrefCount != numFinalGroups;
boolean requiresRemoval = postAddPrefCount != numFinalGroups;
if (hasInsertions || requiresRemoval) {
groupPrefsList.removeAll();
for (PreferenceCategory group : finalOrderedGroups) {
groupPrefsList.addPreference(group);
}
}
}
protected void populateGroupToggle(final PreferenceGroup parent,
NotificationChannelGroup group) {
RestrictedSwitchPreference preference =
new RestrictedSwitchPreference(mContext);
/**
* Looks for the channel preference for the given channel's key at the expected index, if that
* doesn't match, it checks all rows, and if it can't find that channel anywhere, it creates
* the preference.
*/
@NonNull
private MasterSwitchPreference findOrCreateChannelPrefForKey(
@NonNull PreferenceGroup groupPrefGroup, @NonNull String key, int expectedIndex) {
int preferenceCount = groupPrefGroup.getPreferenceCount();
if (expectedIndex < preferenceCount) {
Preference preference = groupPrefGroup.getPreference(expectedIndex);
if (key.equals(preference.getKey())) {
return (MasterSwitchPreference) preference;
}
}
for (int i = 0; i < preferenceCount; i++) {
Preference preference = groupPrefGroup.getPreference(i);
if (key.equals(preference.getKey())) {
preference.setOrder(expectedIndex);
return (MasterSwitchPreference) preference;
}
}
MasterSwitchPreference channelPref = new MasterSwitchPreference(mContext);
channelPref.setOrder(expectedIndex);
channelPref.setKey(key);
groupPrefGroup.addPreference(channelPref);
return channelPref;
}
private void updateGroupPreferences(@NonNull NotificationChannelGroup group,
@NonNull PreferenceGroup groupPrefGroup) {
int initialPrefCount = groupPrefGroup.getPreferenceCount();
List<Preference> finalOrderedPrefs = new ArrayList<>();
if (group.getId() == null) {
// For the 'null' group, set the "Other" title.
groupPrefGroup.setTitle(R.string.notification_channels_other);
} else {
// For an app-defined group, set their name and create a row to toggle 'isBlocked'.
groupPrefGroup.setTitle(group.getName());
finalOrderedPrefs.add(addOrUpdateGroupToggle(groupPrefGroup, group));
}
// Here "empty" means having no channel rows; the group toggle is ignored for this purpose.
boolean initiallyEmpty = groupPrefGroup.getPreferenceCount() == finalOrderedPrefs.size();
// For each channel, add or update the preference object.
final List<NotificationChannel> channels =
group.isBlocked() ? Collections.emptyList() : group.getChannels();
Collections.sort(channels, CHANNEL_COMPARATOR);
for (NotificationChannel channel : channels) {
if (!TextUtils.isEmpty(channel.getConversationId()) && !channel.isDemoted()) {
// conversations get their own section
continue;
}
// Get or create the row, and populate its current state.
MasterSwitchPreference channelPref = findOrCreateChannelPrefForKey(groupPrefGroup,
channel.getId(), /* expectedIndex */ finalOrderedPrefs.size());
updateSingleChannelPrefs(channelPref, channel, group.isBlocked());
finalOrderedPrefs.add(channelPref);
}
int postAddPrefCount = groupPrefGroup.getPreferenceCount();
// If any channels were inserted (into a non-empty list) or need to be removed, we need to
// remove all preferences and re-add them all.
// This is required to ensure proper ordering of inserted channels, and it simplifies logic
// at the cost of computation in the rare case that the list is changing.
int numFinalGroups = finalOrderedPrefs.size();
boolean hasInsertions = !initiallyEmpty && initialPrefCount != numFinalGroups;
boolean requiresRemoval = postAddPrefCount != numFinalGroups;
if (hasInsertions || requiresRemoval) {
groupPrefGroup.removeAll();
for (Preference preference : finalOrderedPrefs) {
groupPrefGroup.addPreference(preference);
}
}
}
/** Add or find and update the toggle for disabling the entire notification channel group. */
private Preference addOrUpdateGroupToggle(@NonNull final PreferenceGroup parent,
@NonNull final NotificationChannelGroup group) {
boolean shouldAdd = false;
final RestrictedSwitchPreference preference;
if (parent.getPreferenceCount() > 0
&& parent.getPreference(0) instanceof RestrictedSwitchPreference) {
preference = (RestrictedSwitchPreference) parent.getPreference(0);
} else {
shouldAdd = true;
preference = new RestrictedSwitchPreference(mContext);
}
preference.setOrder(-1);
preference.setTitle(mContext.getString(
R.string.notification_switch_label, group.getName()));
preference.setEnabled(mAdmin == null
@@ -171,23 +300,26 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
onGroupBlockStateChanged(group);
return true;
});
parent.addPreference(preference);
if (shouldAdd) {
parent.addPreference(preference);
}
return preference;
}
protected Preference populateSingleChannelPrefs(PreferenceGroup parent,
final NotificationChannel channel, final boolean groupBlocked) {
MasterSwitchPreference channelPref = new MasterSwitchPreference(mContext);
/** Update the properties of the channel preference with the values from the channel object. */
private void updateSingleChannelPrefs(@NonNull final MasterSwitchPreference channelPref,
@NonNull final NotificationChannel channel,
final boolean groupBlocked) {
channelPref.setSwitchEnabled(mAdmin == null
&& isChannelBlockable(channel)
&& isChannelConfigurable(channel)
&& !groupBlocked);
channelPref.setIcon(null);
if (channel.getImportance() > IMPORTANCE_LOW) {
channelPref.setIcon(getAlertingIcon());
} else {
channelPref.setIcon(null);
}
channelPref.setIconSize(MasterSwitchPreference.ICON_SIZE_SMALL);
channelPref.setKey(channel.getId());
channelPref.setTitle(channel.getName());
channelPref.setSummary(NotificationBackend.getSentSummary(
mContext, mAppRow.sentByChannel.get(channel.getId()), false));
@@ -219,10 +351,6 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
return true;
});
if (parent.findPreference(channelPref.getKey()) == null) {
parent.addPreference(channelPref);
}
return channelPref;
}
private Drawable getAlertingIcon() {
@@ -235,30 +363,9 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
if (group == null) {
return;
}
PreferenceGroup groupGroup = mPreference.findPreference(group.getId());
if (groupGroup != null) {
if (group.isBlocked()) {
List<Preference> toRemove = new ArrayList<>();
int childCount = groupGroup.getPreferenceCount();
for (int i = 0; i < childCount; i++) {
Preference pref = groupGroup.getPreference(i);
if (pref instanceof MasterSwitchPreference) {
toRemove.add(pref);
}
}
for (Preference pref : toRemove) {
groupGroup.removePreference(pref);
}
} else {
final List<NotificationChannel> channels = group.getChannels();
Collections.sort(channels, CHANNEL_COMPARATOR);
int N = channels.size();
for (int i = 0; i < N; i++) {
final NotificationChannel channel = channels.get(i);
populateSingleChannelPrefs(groupGroup, channel, group.isBlocked());
}
}
PreferenceGroup groupPrefGroup = mPreference.findPreference(group.getId());
if (groupPrefGroup != null) {
updateGroupPreferences(group, groupPrefGroup);
}
}
}

View File

@@ -44,6 +44,7 @@ import com.android.settings.Utils;
import com.android.settingslib.media.InfoMediaDevice;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.media.MediaOutputSliceConstants;
import java.util.ArrayList;
import java.util.List;
@@ -72,7 +73,13 @@ public class MediaOutputPanel implements PanelContent, LocalMediaManager.DeviceC
private MediaController mMediaController;
public static MediaOutputPanel create(Context context, String packageName) {
return new MediaOutputPanel(context, packageName);
// Redirect to new media output dialog
context.sendBroadcast(new Intent()
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
.setPackage(MediaOutputSliceConstants.SYSTEMUI_PACKAGE_NAME)
.setAction(MediaOutputSliceConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG)
.putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME, packageName));
return null;
}
private MediaOutputPanel(Context context, String packageName) {

View File

@@ -442,12 +442,14 @@ public class PanelFragment extends Fragment {
if (mLayoutView != null) {
mLayoutView.getViewTreeObserver().removeOnGlobalLayoutListener(mPanelLayoutListener);
}
mMetricsProvider.action(
0 /* attribution */,
SettingsEnums.PAGE_HIDE,
mPanel.getMetricsCategory(),
mPanelClosedKey,
0 /* value */);
if (mPanel != null) {
mMetricsProvider.action(
0 /* attribution */,
SettingsEnums.PAGE_HIDE,
mPanel.getMetricsCategory(),
mPanelClosedKey,
0 /* value */);
}
}
@VisibleForTesting

View File

@@ -16,6 +16,9 @@
package com.android.settings.panel;
import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI;
import static com.android.settings.slices.CustomSliceRegistry.REMOTE_MEDIA_SLICE_URI;
import static com.android.settings.slices.CustomSliceRegistry.VOLUME_ALARM_URI;
@@ -24,20 +27,40 @@ import static com.android.settings.slices.CustomSliceRegistry.VOLUME_MEDIA_URI;
import static com.android.settings.slices.CustomSliceRegistry.VOLUME_RINGER_URI;
import android.app.settings.SettingsEnums;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.provider.Settings;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import com.android.settings.R;
import com.android.settingslib.media.MediaOutputSliceConstants;
import java.util.ArrayList;
import java.util.List;
public class VolumePanel implements PanelContent {
/**
* Panel data class for Volume settings.
*/
public class VolumePanel implements PanelContent, LifecycleObserver {
private final Context mContext;
private PanelContentCallback mCallback;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (MediaOutputSliceConstants.ACTION_CLOSE_PANEL.equals(intent.getAction())) {
mCallback.forceClose();
}
}
};
public static VolumePanel create(Context context) {
return new VolumePanel(context);
}
@@ -46,6 +69,20 @@ public class VolumePanel implements PanelContent {
mContext = context.getApplicationContext();
}
/** Invoked when the panel is resumed. */
@OnLifecycleEvent(ON_RESUME)
public void onResume() {
final IntentFilter filter = new IntentFilter();
filter.addAction(MediaOutputSliceConstants.ACTION_CLOSE_PANEL);
mContext.registerReceiver(mReceiver, filter);
}
/** Invoked when the panel is paused. */
@OnLifecycleEvent(ON_PAUSE)
public void onPause() {
mContext.unregisterReceiver(mReceiver);
}
@Override
public CharSequence getTitle() {
return mContext.getText(R.string.sound_settings);
@@ -78,4 +115,9 @@ public class VolumePanel implements PanelContent {
public int getViewType() {
return PanelContent.VIEW_TYPE_SLIDER;
}
@Override
public void registerCallback(PanelContentCallback callback) {
mCallback = callback;
}
}

View File

@@ -22,14 +22,13 @@ import android.content.Intent;
import android.media.AudioManager;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.media.MediaOutputUtils;
import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.A2dpProfile;
import com.android.settingslib.bluetooth.HearingAidProfile;
@@ -51,7 +50,8 @@ public class MediaOutputPreferenceController extends AudioSwitchPreferenceContro
public MediaOutputPreferenceController(Context context, String key) {
super(context, key);
mMediaController = getActiveLocalMediaController();
mMediaController = MediaOutputUtils.getActiveLocalMediaController(context.getSystemService(
MediaSessionManager.class));
}
@Override
@@ -133,34 +133,15 @@ public class MediaOutputPreferenceController extends AudioSwitchPreferenceContro
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
if (TextUtils.equals(preference.getKey(), getPreferenceKey())) {
final Intent intent = new Intent()
.setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
mContext.sendBroadcast(new Intent()
.setAction(MediaOutputSliceConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG)
.setPackage(MediaOutputSliceConstants.SYSTEMUI_PACKAGE_NAME)
.putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
mMediaController.getPackageName())
.putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN,
mMediaController.getSessionToken()));
return true;
}
return false;
}
@Nullable
MediaController getActiveLocalMediaController() {
final MediaSessionManager mMediaSessionManager = mContext.getSystemService(
MediaSessionManager.class);
for (MediaController controller : mMediaSessionManager.getActiveSessions(null)) {
final MediaController.PlaybackInfo pi = controller.getPlaybackInfo();
if (pi == null) {
return null;
}
final PlaybackState playbackState = controller.getPlaybackState();
if (playbackState == null) {
return null;
}
if (pi.getPlaybackType() == MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL
&& playbackState.getState() == PlaybackState.STATE_PLAYING) {
return controller;
}
}
return null;
}
}

View File

@@ -23,6 +23,8 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Switch;
import androidx.annotation.Keep;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
@@ -101,6 +103,16 @@ public class MasterSwitchPreference extends RestrictedPreference {
return mSwitch != null && mChecked;
}
/**
* Used to validate the state of mChecked and mCheckedSet when testing, without requiring
* that a ViewHolder be bound to the object.
*/
@Keep
@Nullable
public Boolean getCheckedState() {
return mCheckedSet ? mChecked : null;
}
public void setChecked(boolean checked) {
// Always set checked the first time; don't assume the field's default of false.
final boolean changed = mChecked != checked;

View File

@@ -166,6 +166,7 @@ public class WifiConfigController2 implements TextWatcher,
private ScrollView mDialogContainer;
private Spinner mSecuritySpinner;
@VisibleForTesting Spinner mEapMethodSpinner;
private int mLastShownEapMethod;
@VisibleForTesting Spinner mEapSimSpinner; // For EAP-SIM, EAP-AKA and EAP-AKA-PRIME.
private Spinner mEapCaCertSpinner;
private Spinner mEapOcspSpinner;
@@ -1059,6 +1060,7 @@ public class WifiConfigController2 implements TextWatcher,
final int eapMethod = enterpriseConfig.getEapMethod();
final int phase2Method = enterpriseConfig.getPhase2Method();
mEapMethodSpinner.setSelection(eapMethod);
mLastShownEapMethod = eapMethod;
showEapFieldsByMethod(eapMethod);
switch (eapMethod) {
case Eap.PEAP:
@@ -1630,7 +1632,11 @@ public class WifiConfigController2 implements TextWatcher,
mSsidScanButton.setVisibility(View.GONE);
}
} else if (parent == mEapMethodSpinner) {
showSecurityFields(/* refreshEapMethods */ false, /* refreshCertificates */ true);
final int selectedItemPosition = mEapMethodSpinner.getSelectedItemPosition();
if (mLastShownEapMethod != selectedItemPosition) {
mLastShownEapMethod = selectedItemPosition;
showSecurityFields(/* refreshEapMethods */ false, /* refreshCertificates */ true);
}
} else if (parent == mEapCaCertSpinner) {
showSecurityFields(/* refreshEapMethods */ false, /* refreshCertificates */ false);
} else if (parent == mPhase2Spinner

View File

@@ -36,6 +36,7 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
@@ -78,7 +79,8 @@ public class WifiNetworkDetailsFragment2 extends DashboardFragment implements
private HandlerThread mWorkerThread;
private WifiDetailPreferenceController2 mWifiDetailPreferenceController2;
private List<WifiDialog2.WifiDialog2Listener> mWifiDialogListeners = new ArrayList<>();
private List<AbstractPreferenceController> mControllers;
@VisibleForTesting
List<AbstractPreferenceController> mControllers;
@Override
public void onDestroy() {
@@ -255,6 +257,11 @@ public class WifiNetworkDetailsFragment2 extends DashboardFragment implements
* API call for refreshing the preferences in this fragment.
*/
public void refreshPreferences() {
updatePreferenceStates();
displayPreferenceControllers();
}
protected void displayPreferenceControllers() {
final PreferenceScreen screen = getPreferenceScreen();
for (AbstractPreferenceController controller : mControllers) {
// WifiDetailPreferenceController2 gets the callback WifiEntryCallback#onUpdated,

View File

@@ -69,12 +69,13 @@ public class WifiPrivacyPreferenceController2 extends BasePreferenceController i
public void updateState(Preference preference) {
final DropDownPreference dropDownPreference = (DropDownPreference) preference;
final int randomizationLevel = getRandomizationValue();
final boolean isSelectable = mWifiEntry.canSetPrivacy();
preference.setSelectable(isSelectable);
dropDownPreference.setValue(Integer.toString(randomizationLevel));
updateSummary(dropDownPreference, randomizationLevel);
// Makes preference not selectable, when this is a ephemeral network.
if (!mWifiEntry.canSetPrivacy()) {
preference.setSelectable(false);
// If the preference cannot be selectable, display a temporary network in the summary.
if (!isSelectable) {
dropDownPreference.setSummary(R.string.wifi_privacy_settings_ephemeral_summary);
}
}