Snap for 9254301 from a2f95445dd to udc-release
Change-Id: I0efff55c2204bd2242d1455f5b523d553820d1ff
This commit is contained in:
@@ -14,18 +14,21 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||
android:shape="rectangle">
|
||||
<stroke
|
||||
android:color="?androidprv:attr/colorAccentPrimaryVariant"
|
||||
android:width="1dp"/>
|
||||
<corners android:radius="24dp"/>
|
||||
<padding
|
||||
android:left="16dp"
|
||||
android:right="16dp"
|
||||
android:top="8dp"
|
||||
android:bottom="8dp" />
|
||||
<solid android:color="@android:color/transparent" />
|
||||
</shape>
|
||||
android:insetTop="6dp"
|
||||
android:insetBottom="6dp">
|
||||
<shape
|
||||
android:shape="rectangle">
|
||||
<stroke
|
||||
android:color="?androidprv:attr/colorAccentPrimaryVariant"
|
||||
android:width="1dp"/>
|
||||
<corners android:radius="24dp"/>
|
||||
<padding
|
||||
android:left="16dp"
|
||||
android:right="16dp"
|
||||
android:top="8dp"
|
||||
android:bottom="8dp" />
|
||||
<solid android:color="@android:color/transparent" />
|
||||
</shape>
|
||||
</inset>
|
||||
@@ -15,17 +15,20 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||
android:shape="rectangle">
|
||||
<stroke
|
||||
android:color="@android:color/transparent"
|
||||
android:width="1dp"/>
|
||||
<corners android:radius="20dp"/>
|
||||
<padding
|
||||
android:left="16dp"
|
||||
android:right="16dp"
|
||||
android:top="8dp"
|
||||
android:bottom="8dp" />
|
||||
<solid android:color="?androidprv:attr/colorAccentPrimary" />
|
||||
</shape>
|
||||
android:insetTop="6dp"
|
||||
android:insetBottom="6dp">
|
||||
<shape android:shape="rectangle">
|
||||
<stroke
|
||||
android:color="@android:color/transparent"
|
||||
android:width="1dp"/>
|
||||
<corners android:radius="20dp"/>
|
||||
<padding
|
||||
android:left="16dp"
|
||||
android:right="16dp"
|
||||
android:top="8dp"
|
||||
android:bottom="8dp" />
|
||||
<solid android:color="?androidprv:attr/colorAccentPrimary" />
|
||||
</shape>
|
||||
</inset>
|
||||
@@ -117,7 +117,7 @@
|
||||
android:id="@+id/see_more"
|
||||
style="@style/PanelOptionRoundedOutlinedButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="0dp"
|
||||
android:text="@string/settings_button"/>
|
||||
|
||||
@@ -130,7 +130,7 @@
|
||||
android:id="@+id/done"
|
||||
style="@style/PanelOptionRoundedSolidButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="0dp"
|
||||
android:text="@string/done"/>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -117,6 +117,7 @@ import com.android.settingslib.widget.AdaptiveIcon;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
public final class Utils extends com.android.settingslib.Utils {
|
||||
|
||||
@@ -589,7 +590,9 @@ public final class Utils extends com.android.settingslib.Utils {
|
||||
return inflater.inflate(resId, parent, false);
|
||||
}
|
||||
|
||||
public static ArraySet<String> getHandledDomains(PackageManager pm, String packageName) {
|
||||
/** Gets all the domains that the given package could handled. */
|
||||
@NonNull
|
||||
public static Set<String> getHandledDomains(PackageManager pm, String packageName) {
|
||||
final List<IntentFilterVerificationInfo> iviList =
|
||||
pm.getIntentFilterVerifications(packageName);
|
||||
final List<IntentFilter> filters = pm.getAllIntentFilters(packageName);
|
||||
@@ -597,9 +600,7 @@ public final class Utils extends com.android.settingslib.Utils {
|
||||
final ArraySet<String> result = new ArraySet<>();
|
||||
if (iviList != null && iviList.size() > 0) {
|
||||
for (IntentFilterVerificationInfo ivi : iviList) {
|
||||
for (String host : ivi.getDomains()) {
|
||||
result.add(host);
|
||||
}
|
||||
result.addAll(ivi.getDomains());
|
||||
}
|
||||
}
|
||||
if (filters != null && filters.size() > 0) {
|
||||
|
||||
@@ -23,7 +23,6 @@ import android.app.settings.SettingsEnums;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
@@ -36,6 +35,8 @@ import com.android.settings.Utils;
|
||||
import com.android.settingslib.widget.FooterPreference;
|
||||
import com.android.settingslib.widget.SelectorWithWidgetPreference;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Display the Open Supported Links page. Allow users choose what kind supported links they need.
|
||||
*/
|
||||
@@ -195,7 +196,7 @@ public class OpenSupportedLinks extends AppInfoWithHeader implements
|
||||
|
||||
@VisibleForTesting
|
||||
void addLinksToFooter(FooterPreference footer) {
|
||||
final ArraySet<String> result = Utils.getHandledDomains(mPackageManager, mPackageName);
|
||||
final Set<String> result = Utils.getHandledDomains(mPackageManager, mPackageName);
|
||||
if (result.isEmpty()) {
|
||||
Log.w(TAG, "Can't find any app links.");
|
||||
return;
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.os.AsyncTask;
|
||||
import android.os.BatteryUsageStats;
|
||||
import android.os.Bundle;
|
||||
import android.os.UidBatteryConsumer;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -144,7 +145,7 @@ public class AppBatteryPreferenceController extends BasePreferenceController
|
||||
} else {
|
||||
Log.i(TAG, "Launch : " + mPackageName + " with package name");
|
||||
AdvancedPowerUsageDetail.startBatteryDetailPage(mParent.getActivity(), mParent,
|
||||
mPackageName);
|
||||
mPackageName, UserHandle.CURRENT);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -686,7 +686,8 @@ public class ManageApplications extends InstrumentedFragment
|
||||
break;
|
||||
case LIST_TYPE_BATTERY_OPTIMIZATION:
|
||||
AdvancedPowerUsageDetail.startBatteryDetailPage(
|
||||
getActivity(), this, mCurrentPkgName);
|
||||
getActivity(), this, mCurrentPkgName,
|
||||
UserHandle.getUserHandleForUid(mCurrentUid));
|
||||
break;
|
||||
// TODO: Figure out if there is a way where we can spin up the profile's settings
|
||||
// process ahead of time, to avoid a long load of data when user clicks on a managed
|
||||
|
||||
@@ -24,7 +24,6 @@ import android.bluetooth.BluetoothDevice;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.os.PowerManager;
|
||||
import android.os.UserManager;
|
||||
import android.util.Log;
|
||||
@@ -126,15 +125,8 @@ public final class BluetoothPermissionRequest extends BroadcastReceiver {
|
||||
// Create an intent triggered by clicking on the
|
||||
// "Clear All Notifications" button
|
||||
|
||||
String bluetoothName;
|
||||
try {
|
||||
bluetoothName = Utils.findBluetoothPackageName(context);
|
||||
} catch (NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
Intent deleteIntent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
|
||||
deleteIntent.setPackage(bluetoothName);
|
||||
deleteIntent.setPackage("com.android.bluetooth");
|
||||
deleteIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
|
||||
deleteIntent.putExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
|
||||
BluetoothDevice.CONNECTION_ACCESS_NO);
|
||||
|
||||
51
src/com/android/settings/bluetooth/Utils.java
Normal file → Executable file
51
src/com/android/settings/bluetooth/Utils.java
Normal file → Executable file
@@ -16,18 +16,11 @@
|
||||
|
||||
package com.android.settings.bluetooth;
|
||||
|
||||
import static android.os.Process.BLUETOOTH_UID;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
@@ -196,48 +189,4 @@ public final class Utils {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Bluetooth Package name
|
||||
*/
|
||||
public static String findBluetoothPackageName(Context context)
|
||||
throws NameNotFoundException {
|
||||
// this activity will always be in the package where the rest of Bluetooth lives
|
||||
String sentinelActivity = "com.android.bluetooth.opp.BluetoothOppLauncherActivity";
|
||||
PackageManager packageManager = context.createContextAsUser(UserHandle.SYSTEM, 0)
|
||||
.getPackageManager();
|
||||
String[] allPackages = packageManager.getPackagesForUid(BLUETOOTH_UID);
|
||||
String matchedPackage = null;
|
||||
for (String candidatePackage : allPackages) {
|
||||
PackageInfo packageInfo;
|
||||
try {
|
||||
packageInfo =
|
||||
packageManager.getPackageInfo(
|
||||
candidatePackage,
|
||||
PackageManager.GET_ACTIVITIES
|
||||
| PackageManager.MATCH_ANY_USER
|
||||
| PackageManager.MATCH_UNINSTALLED_PACKAGES
|
||||
| PackageManager.MATCH_DISABLED_COMPONENTS);
|
||||
} catch (NameNotFoundException e) {
|
||||
// rethrow
|
||||
throw e;
|
||||
}
|
||||
if (packageInfo.activities == null) {
|
||||
continue;
|
||||
}
|
||||
for (ActivityInfo activity : packageInfo.activities) {
|
||||
if (sentinelActivity.equals(activity.name)) {
|
||||
if (matchedPackage == null) {
|
||||
matchedPackage = candidatePackage;
|
||||
} else {
|
||||
throw new NameNotFoundException("multiple main bluetooth packages found");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (matchedPackage != null) {
|
||||
return matchedPackage;
|
||||
}
|
||||
throw new NameNotFoundException("Could not find main bluetooth package");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
|
||||
package com.android.settings.development;
|
||||
|
||||
import android.bluetooth.BluetoothManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.os.SystemProperties;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
@@ -41,15 +42,18 @@ public class BluetoothMaxConnectedAudioDevicesPreferenceController extends
|
||||
|
||||
private int mDefaultMaxConnectedAudioDevices = 0;
|
||||
|
||||
private final BluetoothManager mBluetoothManager;
|
||||
|
||||
public BluetoothMaxConnectedAudioDevicesPreferenceController(Context context) {
|
||||
super(context);
|
||||
|
||||
mBluetoothManager = context.getSystemService(BluetoothManager.class);
|
||||
|
||||
mDefaultMaxConnectedAudioDevices =
|
||||
mBluetoothManager.getAdapter().getMaxConnectedAudioDevices();
|
||||
try {
|
||||
Resources res = context.getPackageManager().getResourcesForApplication(
|
||||
"com.android.bluetooth");
|
||||
mDefaultMaxConnectedAudioDevices = res.getInteger(res.getIdentifier(
|
||||
"config_bluetooth_max_connected_audio_devices",
|
||||
"integer", "com.android.bluetooth"));
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -219,7 +219,8 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements
|
||||
* Start packageName's battery detail page.
|
||||
*/
|
||||
public static void startBatteryDetailPage(
|
||||
Activity caller, Instrumentable instrumentable, String packageName) {
|
||||
Activity caller, Instrumentable instrumentable, String packageName,
|
||||
UserHandle userHandle) {
|
||||
final Bundle args = new Bundle(3);
|
||||
final PackageManager packageManager = caller.getPackageManager();
|
||||
args.putString(EXTRA_PACKAGE_NAME, packageName);
|
||||
@@ -235,6 +236,7 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements
|
||||
.setTitleRes(R.string.battery_details_title)
|
||||
.setArguments(args)
|
||||
.setSourceMetricsCategory(instrumentable.getMetricsCategory())
|
||||
.setUserHandle(userHandle)
|
||||
.launch();
|
||||
}
|
||||
|
||||
@@ -475,16 +477,12 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements
|
||||
private CharSequence getAppActiveTime(Bundle bundle) {
|
||||
final long foregroundTimeMs = bundle.getLong(EXTRA_FOREGROUND_TIME);
|
||||
final long backgroundTimeMs = bundle.getLong(EXTRA_BACKGROUND_TIME);
|
||||
final int consumedPower = bundle.getInt(EXTRA_POWER_USAGE_AMOUNT);
|
||||
final int uid = bundle.getInt(EXTRA_UID, 0);
|
||||
final String slotTime = bundle.getString(EXTRA_SLOT_TIME, null);
|
||||
final long totalTimeMs = foregroundTimeMs + backgroundTimeMs;
|
||||
final CharSequence usageTimeSummary;
|
||||
|
||||
if (totalTimeMs == 0) {
|
||||
usageTimeSummary = getText(
|
||||
consumedPower > 0 ? R.string.battery_usage_without_time
|
||||
: R.string.battery_not_usage);
|
||||
usageTimeSummary = getText(R.string.battery_usage_without_time);
|
||||
} else if (slotTime == null) {
|
||||
// Shows summary text with last full charge if slot time is null.
|
||||
usageTimeSummary = getAppFullChargeActiveSummary(
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.settings.notification;
|
||||
|
||||
import android.app.ActivityThread;
|
||||
import android.app.INotificationManager;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
@@ -29,26 +30,32 @@ import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.Vibrator;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.service.notification.NotificationListenerService;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.lifecycle.OnLifecycleEvent;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.Utils;
|
||||
import com.android.settingslib.core.lifecycle.Lifecycle;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Update notification volume icon in Settings in response to user adjusting volume
|
||||
* Update notification volume icon in Settings in response to user adjusting volume.
|
||||
*/
|
||||
public class NotificationVolumePreferenceController extends VolumeSeekBarPreferenceController {
|
||||
|
||||
private static final String TAG = "NotificationVolumePreferenceController";
|
||||
private static final String KEY_NOTIFICATION_VOLUME = "notification_volume";
|
||||
private static final boolean CONFIG_DEFAULT_VAL = false;
|
||||
private boolean mSeparateNotification;
|
||||
|
||||
private Vibrator mVibrator;
|
||||
private int mRingerMode = AudioManager.RINGER_MODE_NORMAL;
|
||||
@@ -56,39 +63,74 @@ public class NotificationVolumePreferenceController extends VolumeSeekBarPrefere
|
||||
private final RingReceiver mReceiver = new RingReceiver();
|
||||
private final H mHandler = new H();
|
||||
private INotificationManager mNoMan;
|
||||
|
||||
|
||||
private int mMuteIcon;
|
||||
private final int mNormalIconId = R.drawable.ic_notifications;
|
||||
private final int mVibrateIconId = R.drawable.ic_volume_ringer_vibrate;
|
||||
private final int mSilentIconId = R.drawable.ic_notifications_off_24dp;
|
||||
|
||||
private final boolean mRingNotificationAliased;
|
||||
|
||||
|
||||
public NotificationVolumePreferenceController(Context context) {
|
||||
this(context, KEY_NOTIFICATION_VOLUME);
|
||||
}
|
||||
|
||||
public NotificationVolumePreferenceController(Context context, String key) {
|
||||
super(context, key);
|
||||
|
||||
mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
|
||||
if (mVibrator != null && !mVibrator.hasVibrator()) {
|
||||
mVibrator = null;
|
||||
}
|
||||
|
||||
mRingNotificationAliased = mContext.getResources().getBoolean(
|
||||
com.android.internal.R.bool.config_alias_ring_notif_stream_types);
|
||||
updateRingerMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow for notification slider to be enabled in the scenario where the config switches on
|
||||
* while settings page is already on the screen by always configuring the preference, even if it
|
||||
* is currently inactive.
|
||||
*/
|
||||
@Override
|
||||
public void displayPreference(PreferenceScreen screen) {
|
||||
super.displayPreference(screen);
|
||||
if (mPreference == null) {
|
||||
setupVolPreference(screen);
|
||||
}
|
||||
mSeparateNotification = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL);
|
||||
if (mPreference != null) {
|
||||
mPreference.setVisible(getAvailabilityStatus() == AVAILABLE);
|
||||
}
|
||||
updateEffectsSuppressor();
|
||||
updatePreferenceIconAndSliderState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Only display the notification slider when the corresponding device config flag is set
|
||||
*/
|
||||
private void onDeviceConfigChange(DeviceConfig.Properties properties) {
|
||||
Set<String> changeSet = properties.getKeyset();
|
||||
|
||||
if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) {
|
||||
boolean newVal = properties.getBoolean(
|
||||
SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL);
|
||||
if (newVal != mSeparateNotification) {
|
||||
mSeparateNotification = newVal;
|
||||
// manually hiding the preference because being unavailable does not do the job
|
||||
if (mPreference != null) {
|
||||
mPreference.setVisible(getAvailabilityStatus() == AVAILABLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
mReceiver.register(true);
|
||||
updateEffectsSuppressor();
|
||||
updatePreferenceIconAndSliderState();
|
||||
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
ActivityThread.currentApplication().getMainExecutor(),
|
||||
this::onDeviceConfigChange);
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
||||
@@ -96,16 +138,17 @@ public class NotificationVolumePreferenceController extends VolumeSeekBarPrefere
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
mReceiver.register(false);
|
||||
DeviceConfig.removeOnPropertiesChangedListener(this::onDeviceConfigChange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAvailabilityStatus() {
|
||||
boolean separateNotification = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
|
||||
|
||||
// Show separate notification slider if ring/notification are not aliased by AudioManager --
|
||||
// if they are, notification volume is controlled by RingVolumePreferenceController.
|
||||
return mContext.getResources().getBoolean(R.bool.config_show_notification_volume)
|
||||
&& (!mRingNotificationAliased || !Utils.isVoiceCapable(mContext))
|
||||
&& !mHelper.isSingleVolume()
|
||||
&& (separateNotification || !Utils.isVoiceCapable(mContext))
|
||||
? AVAILABLE : UNSUPPORTED_ON_DEVICE;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.settings.notification;
|
||||
|
||||
import android.app.ActivityThread;
|
||||
import android.app.INotificationManager;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
@@ -29,6 +30,7 @@ import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.Vibrator;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.service.notification.NotificationListenerService;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
@@ -36,11 +38,13 @@ import android.util.Log;
|
||||
import androidx.lifecycle.OnLifecycleEvent;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.Utils;
|
||||
import com.android.settingslib.core.lifecycle.Lifecycle;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This slider can represent both ring and notification, if the corresponding streams are aliased,
|
||||
@@ -59,24 +63,21 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr
|
||||
|
||||
private int mMuteIcon;
|
||||
|
||||
/*
|
||||
* Whether ring and notification streams are aliased together by AudioManager.
|
||||
* If they are, we'll present one volume control for both.
|
||||
* If not, we'll present separate volume controls.
|
||||
*/
|
||||
private final boolean mRingAliasNotif;
|
||||
|
||||
private final int mNormalIconId;
|
||||
private int mNormalIconId;
|
||||
@VisibleForTesting
|
||||
final int mVibrateIconId;
|
||||
int mVibrateIconId;
|
||||
@VisibleForTesting
|
||||
final int mSilentIconId;
|
||||
int mSilentIconId;
|
||||
|
||||
@VisibleForTesting
|
||||
final int mTitleId;
|
||||
int mTitleId;
|
||||
|
||||
private boolean mSeparateNotification;
|
||||
|
||||
private INotificationManager mNoMan;
|
||||
|
||||
private static final boolean CONFIG_DEFAULT_VAL = false;
|
||||
|
||||
public RingVolumePreferenceController(Context context) {
|
||||
this(context, KEY_RING_VOLUME);
|
||||
}
|
||||
@@ -87,29 +88,56 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr
|
||||
if (mVibrator != null && !mVibrator.hasVibrator()) {
|
||||
mVibrator = null;
|
||||
}
|
||||
|
||||
mRingAliasNotif = isRingAliasNotification();
|
||||
if (mRingAliasNotif) {
|
||||
mTitleId = R.string.ring_volume_option_title;
|
||||
|
||||
mNormalIconId = R.drawable.ic_notifications;
|
||||
mSilentIconId = R.drawable.ic_notifications_off_24dp;
|
||||
} else {
|
||||
mTitleId = R.string.separate_ring_volume_option_title;
|
||||
|
||||
mNormalIconId = R.drawable.ic_ring_volume;
|
||||
mSilentIconId = R.drawable.ic_ring_volume_off;
|
||||
}
|
||||
// todo: set a distinct vibrate icon for ring vs notification
|
||||
mVibrateIconId = R.drawable.ic_volume_ringer_vibrate;
|
||||
|
||||
mSeparateNotification = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL);
|
||||
loadPreferenceIconResources(mSeparateNotification);
|
||||
updateRingerMode();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean isRingAliasNotification() {
|
||||
return mContext.getResources().getBoolean(
|
||||
com.android.internal.R.bool.config_alias_ring_notif_stream_types);
|
||||
private void loadPreferenceIconResources(boolean separateNotification) {
|
||||
if (separateNotification) {
|
||||
mTitleId = R.string.separate_ring_volume_option_title;
|
||||
mNormalIconId = R.drawable.ic_ring_volume;
|
||||
mSilentIconId = R.drawable.ic_ring_volume_off;
|
||||
} else {
|
||||
mTitleId = R.string.ring_volume_option_title;
|
||||
mNormalIconId = R.drawable.ic_notifications;
|
||||
mSilentIconId = R.drawable.ic_notifications_off_24dp;
|
||||
}
|
||||
// todo: set a distinct vibrate icon for ring vs notification
|
||||
mVibrateIconId = R.drawable.ic_volume_ringer_vibrate;
|
||||
}
|
||||
|
||||
/**
|
||||
* As the responsibility of this slider changes, so should its title & icon
|
||||
*/
|
||||
public void onDeviceConfigChange(DeviceConfig.Properties properties) {
|
||||
Set<String> changeSet = properties.getKeyset();
|
||||
if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) {
|
||||
boolean valueUpdated = readSeparateNotificationVolumeConfig();
|
||||
if (valueUpdated) {
|
||||
updateEffectsSuppressor();
|
||||
selectPreferenceIconState();
|
||||
setPreferenceTitle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* side effect: updates the cached value of the config, and also the icon
|
||||
* @return has the config changed?
|
||||
*/
|
||||
private boolean readSeparateNotificationVolumeConfig() {
|
||||
boolean newVal = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL);
|
||||
|
||||
boolean valueUpdated = newVal != mSeparateNotification;
|
||||
if (valueUpdated) {
|
||||
mSeparateNotification = newVal;
|
||||
loadPreferenceIconResources(newVal);
|
||||
}
|
||||
|
||||
return valueUpdated;
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||
@@ -117,8 +145,11 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
mReceiver.register(true);
|
||||
readSeparateNotificationVolumeConfig();
|
||||
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
ActivityThread.currentApplication().getMainExecutor(), this::onDeviceConfigChange);
|
||||
updateEffectsSuppressor();
|
||||
updatePreferenceIcon();
|
||||
selectPreferenceIconState();
|
||||
setPreferenceTitle();
|
||||
}
|
||||
|
||||
@@ -127,6 +158,7 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
mReceiver.register(false);
|
||||
DeviceConfig.removeOnPropertiesChangedListener(this::onDeviceConfigChange);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -170,7 +202,7 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr
|
||||
final int ringerMode = mHelper.getRingerModeInternal();
|
||||
if (mRingerMode == ringerMode) return;
|
||||
mRingerMode = ringerMode;
|
||||
updatePreferenceIcon();
|
||||
selectPreferenceIconState();
|
||||
}
|
||||
|
||||
private void updateEffectsSuppressor() {
|
||||
@@ -190,7 +222,8 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr
|
||||
return;
|
||||
}
|
||||
|
||||
if (hintsMatch(hints, mRingAliasNotif)) {
|
||||
if (hintsMatch(hints, DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false))) {
|
||||
mSuppressor = suppressor;
|
||||
if (mPreference != null) {
|
||||
final String text = SuppressorHelper.getSuppressionText(mContext, suppressor);
|
||||
@@ -200,11 +233,11 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean hintsMatch(int hints, boolean ringNotificationAliased) {
|
||||
boolean hintsMatch(int hints, boolean notificationSeparated) {
|
||||
return (hints & NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS) != 0
|
||||
|| (hints & NotificationListenerService.HINT_HOST_DISABLE_EFFECTS) != 0
|
||||
|| ((hints & NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS)
|
||||
!= 0 && ringNotificationAliased);
|
||||
!= 0 && !notificationSeparated);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -217,7 +250,7 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr
|
||||
mVibrator = vibrator;
|
||||
}
|
||||
|
||||
private void updatePreferenceIcon() {
|
||||
private void selectPreferenceIconState() {
|
||||
if (mPreference != null) {
|
||||
if (mRingerMode == AudioManager.RINGER_MODE_NORMAL) {
|
||||
mPreference.showIcon(mNormalIconId);
|
||||
|
||||
@@ -55,13 +55,17 @@ public abstract class VolumeSeekBarPreferenceController extends
|
||||
public void displayPreference(PreferenceScreen screen) {
|
||||
super.displayPreference(screen);
|
||||
if (isAvailable()) {
|
||||
mPreference = screen.findPreference(getPreferenceKey());
|
||||
mPreference.setCallback(mVolumePreferenceCallback);
|
||||
mPreference.setStream(getAudioStream());
|
||||
mPreference.setMuteIcon(getMuteIcon());
|
||||
setupVolPreference(screen);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setupVolPreference(PreferenceScreen screen) {
|
||||
mPreference = screen.findPreference(getPreferenceKey());
|
||||
mPreference.setCallback(mVolumePreferenceCallback);
|
||||
mPreference.setStream(getAudioStream());
|
||||
mPreference.setMuteIcon(getMuteIcon());
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||
public void onResume() {
|
||||
if (mPreference != null) {
|
||||
|
||||
@@ -98,7 +98,7 @@ private fun AppInfoSettings(packageInfoPresenter: PackageInfoPresenter) {
|
||||
// TODO: notification_settings
|
||||
AppPermissionPreference(app)
|
||||
AppStoragePreference(app)
|
||||
// TODO: instant_app_launch_supported_domain_urls
|
||||
InstantAppDomainsPreference(app)
|
||||
AppDataUsagePreference(app)
|
||||
AppTimeSpentPreference(app)
|
||||
AppBatteryPreference(app)
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.spa.app.appinfo
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.android.settings.R
|
||||
import com.android.settings.Utils
|
||||
import com.android.settingslib.spa.framework.compose.collectAsStateWithLifecycle
|
||||
import com.android.settingslib.spa.framework.theme.SettingsDimension
|
||||
import com.android.settingslib.spa.widget.preference.Preference
|
||||
import com.android.settingslib.spa.widget.preference.PreferenceModel
|
||||
import com.android.settingslib.spaprivileged.framework.common.asUser
|
||||
import com.android.settingslib.spaprivileged.model.app.userHandle
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@Composable
|
||||
fun InstantAppDomainsPreference(app: ApplicationInfo) {
|
||||
val context = LocalContext.current
|
||||
if (!app.isInstantApp) return
|
||||
|
||||
val presenter = remember { InstantAppDomainsPresenter(context, app) }
|
||||
var openDialog by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
Preference(object : PreferenceModel {
|
||||
override val title = stringResource(R.string.app_launch_supported_domain_urls_title)
|
||||
override val summary = presenter.summaryFlow.collectAsStateWithLifecycle(
|
||||
initialValue = stringResource(R.string.summary_placeholder),
|
||||
)
|
||||
override val onClick = { openDialog = true }
|
||||
})
|
||||
|
||||
val domainsState = presenter.domainsFlow.collectAsStateWithLifecycle(initialValue = emptySet())
|
||||
if (openDialog) {
|
||||
Dialog(domainsState) {
|
||||
openDialog = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Dialog(domainsState: State<Set<String>>, onDismissRequest: () -> Unit) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
confirmButton = {},
|
||||
title = {
|
||||
Text(stringResource(R.string.app_launch_supported_domain_urls_title))
|
||||
},
|
||||
text = {
|
||||
Column {
|
||||
domainsState.value.forEach { domain ->
|
||||
Text(
|
||||
text = domain,
|
||||
modifier = Modifier.padding(vertical = SettingsDimension.itemPaddingAround),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private class InstantAppDomainsPresenter(
|
||||
private val context: Context,
|
||||
private val app: ApplicationInfo,
|
||||
) {
|
||||
private val userContext = context.asUser(app.userHandle)
|
||||
private val userPackageManager = userContext.packageManager
|
||||
|
||||
val domainsFlow = flow {
|
||||
emit(Utils.getHandledDomains(userPackageManager, app.packageName))
|
||||
}.flowOn(Dispatchers.IO)
|
||||
|
||||
val summaryFlow = domainsFlow.map { entries ->
|
||||
when (entries.size) {
|
||||
0 -> context.getString(R.string.domain_urls_summary_none)
|
||||
1 -> context.getString(R.string.domain_urls_summary_one, entries.first())
|
||||
else -> context.getString(R.string.domain_urls_summary_some, entries.first())
|
||||
}
|
||||
}.flowOn(Dispatchers.IO)
|
||||
}
|
||||
@@ -24,9 +24,9 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import android.os.SystemProperties;
|
||||
|
||||
@@ -54,11 +54,6 @@ public class BluetoothMaxConnectedAudioDevicesPreferenceControllerTest {
|
||||
@Spy
|
||||
private Context mSpyContext = RuntimeEnvironment.application;
|
||||
|
||||
@Mock
|
||||
private BluetoothManager mBluetoothManager;
|
||||
@Mock
|
||||
private BluetoothAdapter mBluetoothAdapter;
|
||||
|
||||
private ListPreference mPreference;
|
||||
private BluetoothMaxConnectedAudioDevicesPreferenceController mController;
|
||||
|
||||
@@ -68,15 +63,19 @@ public class BluetoothMaxConnectedAudioDevicesPreferenceControllerTest {
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
doReturn(mBluetoothManager).when(mSpyContext).getSystemService(BluetoothManager.class);
|
||||
doReturn(mBluetoothAdapter).when(mBluetoothManager).getAdapter();
|
||||
// Get XML values without mock
|
||||
// Setup test list preference using XML values
|
||||
mPreference = new ListPreference(mSpyContext);
|
||||
mPreference.setEntries(R.array.bluetooth_max_connected_audio_devices);
|
||||
mPreference.setEntryValues(R.array.bluetooth_max_connected_audio_devices_values);
|
||||
doReturn(TEST_MAX_CONNECTED_AUDIO_DEVICES).when(mBluetoothAdapter)
|
||||
.getMaxConnectedAudioDevices();
|
||||
// Retrieve default max connected audio devices to a test controlled value
|
||||
try {
|
||||
Resources res = mSpyContext.getPackageManager().getResourcesForApplication("com.android.bluetooth");
|
||||
TEST_MAX_CONNECTED_AUDIO_DEVICES = res.getInteger(res.getIdentifier("config_bluetooth_max_connected_audio_devices", "integer", "com.android.bluetooth"));
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// Init the actual controller
|
||||
mController = new BluetoothMaxConnectedAudioDevicesPreferenceController(mSpyContext);
|
||||
// Construct preference in the controller via a mocked preference screen object
|
||||
|
||||
@@ -280,8 +280,7 @@ public class AdvancedPowerUsageDetailTest {
|
||||
|
||||
ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
|
||||
verify(mEntityHeaderController).setSummary(captor.capture());
|
||||
assertThat(captor.getValue().toString())
|
||||
.isEqualTo("No usage from last full charge");
|
||||
assertThat(captor.getValue().toString()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -571,7 +570,8 @@ public class AdvancedPowerUsageDetailTest {
|
||||
public void startBatteryDetailPage_noBatteryUsage_hasBasicData() {
|
||||
final ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
|
||||
|
||||
AdvancedPowerUsageDetail.startBatteryDetailPage(mActivity, mFragment, PACKAGE_NAME[0]);
|
||||
AdvancedPowerUsageDetail.startBatteryDetailPage(
|
||||
mActivity, mFragment, PACKAGE_NAME[0], UserHandle.OWNER);
|
||||
|
||||
verify(mActivity).startActivity(captor.capture());
|
||||
|
||||
@@ -589,7 +589,8 @@ public class AdvancedPowerUsageDetailTest {
|
||||
PackageManager.NameNotFoundException {
|
||||
doReturn(UID).when(mPackageManager).getPackageUid(PACKAGE_NAME[0], 0 /* no flag */);
|
||||
|
||||
AdvancedPowerUsageDetail.startBatteryDetailPage(mActivity, mFragment, PACKAGE_NAME[0]);
|
||||
AdvancedPowerUsageDetail.startBatteryDetailPage(
|
||||
mActivity, mFragment, PACKAGE_NAME[0], UserHandle.OWNER);
|
||||
|
||||
assertThat(mBundle.getInt(AdvancedPowerUsageDetail.EXTRA_UID)).isEqualTo(UID);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.settings.notification;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -25,10 +26,17 @@ import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Vibrator;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.service.notification.NotificationListenerService;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
import com.android.internal.R;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
import com.android.settings.testutils.shadow.ShadowDeviceConfig;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -37,11 +45,12 @@ import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.Shadows;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {ShadowDeviceConfig.class})
|
||||
public class NotificationVolumePreferenceControllerTest {
|
||||
|
||||
@Mock
|
||||
private AudioHelper mHelper;
|
||||
@Mock
|
||||
@@ -52,6 +61,11 @@ public class NotificationVolumePreferenceControllerTest {
|
||||
private Vibrator mVibrator;
|
||||
@Mock
|
||||
private Resources mResources;
|
||||
@Mock
|
||||
private PreferenceManager mPreferenceManager;
|
||||
|
||||
private static final String READ_DEVICE_CONFIG_PERMISSION =
|
||||
"android.permission.READ_DEVICE_CONFIG";
|
||||
|
||||
private Context mContext;
|
||||
private NotificationVolumePreferenceController mController;
|
||||
@@ -87,7 +101,9 @@ public class NotificationVolumePreferenceControllerTest {
|
||||
public void isAvailable_voiceCapable_aliasedWithRing_shouldReturnFalse() {
|
||||
when(mResources.getBoolean(
|
||||
com.android.settings.R.bool.config_show_notification_volume)).thenReturn(true);
|
||||
when(mResources.getBoolean(R.bool.config_alias_ring_notif_stream_types)).thenReturn(true);
|
||||
|
||||
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
|
||||
|
||||
NotificationVolumePreferenceController controller =
|
||||
new NotificationVolumePreferenceController(mContext);
|
||||
@@ -105,7 +121,9 @@ public class NotificationVolumePreferenceControllerTest {
|
||||
public void isAvailable_voiceCapable_separatedFromRing_shouldReturnTrue() {
|
||||
when(mResources.getBoolean(
|
||||
com.android.settings.R.bool.config_show_notification_volume)).thenReturn(true);
|
||||
when(mResources.getBoolean(R.bool.config_alias_ring_notif_stream_types)).thenReturn(false);
|
||||
|
||||
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false);
|
||||
|
||||
NotificationVolumePreferenceController controller =
|
||||
new NotificationVolumePreferenceController(mContext);
|
||||
@@ -170,4 +188,70 @@ public class NotificationVolumePreferenceControllerTest {
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enableSeparateNotificationConfig_controllerBecomesAvailable() {
|
||||
PreferenceScreen screen = spy(new PreferenceScreen(mContext, null));
|
||||
VolumeSeekBarPreference volumeSeekBarPreference = mock(VolumeSeekBarPreference.class);
|
||||
when(screen.getPreferenceManager()).thenReturn(mPreferenceManager);
|
||||
when(screen.getContext()).thenReturn(mContext);
|
||||
when(mResources.getBoolean(
|
||||
com.android.settings.R.bool.config_show_notification_volume)).thenReturn(true);
|
||||
// block the alternative condition to enable controller
|
||||
when(mTelephonyManager.isVoiceCapable()).thenReturn(true);
|
||||
|
||||
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
|
||||
|
||||
NotificationVolumePreferenceController controller =
|
||||
new NotificationVolumePreferenceController(mContext);
|
||||
when(screen.findPreference(controller.getPreferenceKey()))
|
||||
.thenReturn(volumeSeekBarPreference);
|
||||
|
||||
// allow the controller to subscribe
|
||||
Shadows.shadowOf((android.app.Application) ApplicationProvider.getApplicationContext())
|
||||
.grantPermissions(READ_DEVICE_CONFIG_PERMISSION);
|
||||
controller.onResume();
|
||||
controller.displayPreference(screen);
|
||||
|
||||
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, Boolean.toString(true),
|
||||
false);
|
||||
|
||||
assertThat(controller.getAvailabilityStatus()
|
||||
== BasePreferenceController.AVAILABLE).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disableSeparateNotificationConfig_controllerBecomesUnavailable() {
|
||||
PreferenceScreen screen = spy(new PreferenceScreen(mContext, null));
|
||||
VolumeSeekBarPreference volumeSeekBarPreference = mock(VolumeSeekBarPreference.class);
|
||||
when(screen.getPreferenceManager()).thenReturn(mPreferenceManager);
|
||||
when(screen.getContext()).thenReturn(mContext);
|
||||
when(mResources.getBoolean(
|
||||
com.android.settings.R.bool.config_show_notification_volume)).thenReturn(true);
|
||||
|
||||
// block the alternative condition to enable controller
|
||||
when(mTelephonyManager.isVoiceCapable()).thenReturn(true);
|
||||
|
||||
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false);
|
||||
|
||||
NotificationVolumePreferenceController controller =
|
||||
new NotificationVolumePreferenceController(mContext);
|
||||
|
||||
when(screen.findPreference(controller.getPreferenceKey()))
|
||||
.thenReturn(volumeSeekBarPreference);
|
||||
|
||||
Shadows.shadowOf((android.app.Application) ApplicationProvider.getApplicationContext())
|
||||
.grantPermissions(READ_DEVICE_CONFIG_PERMISSION);
|
||||
controller.onResume();
|
||||
controller.displayPreference(screen);
|
||||
|
||||
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
|
||||
|
||||
assertThat(controller.getAvailabilityStatus()
|
||||
== BasePreferenceController.UNSUPPORTED_ON_DEVICE).isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,10 +27,13 @@ import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Vibrator;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.service.notification.NotificationListenerService;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.testutils.shadow.ShadowDeviceConfig;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -39,9 +42,11 @@ import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadows.ShadowApplication;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {ShadowDeviceConfig.class})
|
||||
public class RingVolumePreferenceControllerTest {
|
||||
|
||||
@Mock
|
||||
@@ -124,9 +129,10 @@ public class RingVolumePreferenceControllerTest {
|
||||
// todo: verify that the title change is displayed, by examining the underlying preference
|
||||
@Test
|
||||
public void ringNotificationStreamsNotAliased_sliderTitleSetToRingOnly() {
|
||||
when(mResources.getBoolean(
|
||||
com.android.internal.R.bool.config_alias_ring_notif_stream_types))
|
||||
.thenReturn(false);
|
||||
|
||||
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false);
|
||||
|
||||
final RingVolumePreferenceController controller =
|
||||
new RingVolumePreferenceController(mContext);
|
||||
|
||||
@@ -138,8 +144,9 @@ public class RingVolumePreferenceControllerTest {
|
||||
@Test
|
||||
public void ringNotificationStreamsAliased_sliderTitleIncludesBothRingNotification() {
|
||||
|
||||
when(mResources.getBoolean(
|
||||
com.android.internal.R.bool.config_alias_ring_notif_stream_types)).thenReturn(true);
|
||||
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||
SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
|
||||
|
||||
final RingVolumePreferenceController control = new RingVolumePreferenceController(mContext);
|
||||
|
||||
int expectedTitleId = R.string.ring_volume_option_title;
|
||||
@@ -150,39 +157,39 @@ public class RingVolumePreferenceControllerTest {
|
||||
@Test
|
||||
public void setHintsRing_aliased_Matches() {
|
||||
assertThat(mController.hintsMatch(
|
||||
NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS, true)).isTrue();
|
||||
NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS, false)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setHintsRingNotification_aliased_Matches() {
|
||||
assertThat(mController.hintsMatch(NotificationListenerService.HINT_HOST_DISABLE_EFFECTS,
|
||||
true)).isTrue();
|
||||
false)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setHintNotification_aliased_Matches() {
|
||||
assertThat(mController
|
||||
.hintsMatch(NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS,
|
||||
true)).isTrue();
|
||||
false)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setHintsRing_unaliased_Matches() {
|
||||
assertThat(mController.hintsMatch(
|
||||
NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS, false)).isTrue();
|
||||
NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS, true)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setHintsRingNotification_unaliased_Matches() {
|
||||
assertThat(mController.hintsMatch(NotificationListenerService.HINT_HOST_DISABLE_EFFECTS,
|
||||
false)).isTrue();
|
||||
true)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setHintNotification_unaliased_doesNotMatch() {
|
||||
assertThat(mController
|
||||
.hintsMatch(NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS,
|
||||
false)).isFalse();
|
||||
true)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.spa.app.appinfo
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.assertIsEnabled
|
||||
import androidx.compose.ui.test.assertIsNotDisplayed
|
||||
import androidx.compose.ui.test.filterToOne
|
||||
import androidx.compose.ui.test.hasAnyAncestor
|
||||
import androidx.compose.ui.test.hasText
|
||||
import androidx.compose.ui.test.isDialog
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.onRoot
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.dx.mockito.inline.extended.ExtendedMockito
|
||||
import com.android.settings.R
|
||||
import com.android.settings.Utils
|
||||
import com.android.settings.testutils.delay
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.any
|
||||
import org.mockito.Mockito.anyInt
|
||||
import org.mockito.MockitoSession
|
||||
import org.mockito.Spy
|
||||
import org.mockito.quality.Strictness
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class InstantAppDomainsPreferenceTest {
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
private lateinit var mockSession: MockitoSession
|
||||
|
||||
@Spy
|
||||
private val context: Context = ApplicationProvider.getApplicationContext()
|
||||
|
||||
@Mock
|
||||
private lateinit var packageManager: PackageManager
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
mockSession = ExtendedMockito.mockitoSession()
|
||||
.initMocks(this)
|
||||
.mockStatic(Utils::class.java)
|
||||
.strictness(Strictness.LENIENT)
|
||||
.startMocking()
|
||||
whenever(context.packageManager).thenReturn(packageManager)
|
||||
Mockito.doReturn(context).`when`(context).createContextAsUser(any(), anyInt())
|
||||
mockDomains(emptySet())
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
mockSession.finishMocking()
|
||||
}
|
||||
|
||||
private fun mockDomains(domains: Set<String>) {
|
||||
whenever(Utils.getHandledDomains(packageManager, PACKAGE_NAME)).thenReturn(domains)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun notInstantApp_notDisplayed() {
|
||||
val app = ApplicationInfo()
|
||||
|
||||
setContent(app)
|
||||
|
||||
composeTestRule.onRoot().assertIsNotDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun title_displayed() {
|
||||
setContent()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText(context.getString(R.string.app_launch_supported_domain_urls_title))
|
||||
.assertIsDisplayed()
|
||||
.assertIsEnabled()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun noDomain() {
|
||||
mockDomains(emptySet())
|
||||
|
||||
setContent()
|
||||
|
||||
composeTestRule.onNodeWithText(context.getString(R.string.domain_urls_summary_none))
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun oneDomain() {
|
||||
mockDomains(setOf("abc"))
|
||||
|
||||
setContent()
|
||||
|
||||
composeTestRule.onNodeWithText("Open abc").assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun twoDomains() {
|
||||
mockDomains(setOf("abc", "def"))
|
||||
|
||||
setContent()
|
||||
|
||||
composeTestRule.onNodeWithText("Open abc and other URLs").assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenClicked() {
|
||||
mockDomains(setOf("abc", "def"))
|
||||
|
||||
setContent()
|
||||
composeTestRule.onRoot().performClick()
|
||||
composeTestRule.delay()
|
||||
|
||||
assertDialogHasText(context.getString(R.string.app_launch_supported_domain_urls_title))
|
||||
assertDialogHasText("abc")
|
||||
assertDialogHasText("def")
|
||||
}
|
||||
|
||||
private fun assertDialogHasText(text: String) {
|
||||
composeTestRule.onAllNodes(hasAnyAncestor(isDialog()))
|
||||
.filterToOne(hasText(text))
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
private fun setContent(app:ApplicationInfo = INSTANT_APP) {
|
||||
composeTestRule.setContent {
|
||||
CompositionLocalProvider(LocalContext provides context) {
|
||||
InstantAppDomainsPreference(app)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val PACKAGE_NAME = "package.name"
|
||||
const val UID = 123
|
||||
|
||||
val INSTANT_APP = ApplicationInfo().apply {
|
||||
packageName = PACKAGE_NAME
|
||||
uid = UID
|
||||
privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user