Merge RQ2A.210305.007
Bug: 180401296 Merged-In: I12e20519ac29f802f9983dcf27f313388b33829c Change-Id: I8ca0bb7173f6abe08596422b281f6913b66ab9b7
This commit is contained in:
@@ -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
|
||||
|
@@ -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));
|
||||
}
|
||||
|
@@ -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();
|
||||
|
@@ -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();
|
||||
|
@@ -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 */);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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));
|
||||
|
@@ -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 */);
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
*
|
||||
|
@@ -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);
|
||||
});
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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.
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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.
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
@@ -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";
|
||||
|
46
src/com/android/settings/media/BluetoothPairingReceiver.java
Normal file
46
src/com/android/settings/media/BluetoothPairingReceiver.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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));
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -396,7 +396,7 @@ public class MediaOutputSlice implements CustomSliceable {
|
||||
|
||||
@Override
|
||||
public Class getBackgroundWorkerClass() {
|
||||
return MediaOutputSliceWorker.class;
|
||||
return MediaDeviceUpdateWorker.class;
|
||||
}
|
||||
|
||||
private boolean isVisible() {
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
90
src/com/android/settings/media/MediaOutputUtils.java
Normal file
90
src/com/android/settings/media/MediaOutputUtils.java
Normal 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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
|
@@ -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,
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user