Snap for 11876238 from 7f541e460b to 24Q3-release
Change-Id: I6c71eff6d3db72ea15720ae25064d666e142a9f7
This commit is contained in:
@@ -17,9 +17,6 @@
|
||||
},
|
||||
{
|
||||
"exclude-filter": "com.android.settings.regionalpreferences"
|
||||
},
|
||||
{
|
||||
"exclude-filter": "com.android.settings.vpn2"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -84,11 +84,11 @@
|
||||
<!-- Introduction detail message shown in face enrollment dialog [CHAR LIMIT=NONE]-->
|
||||
<string name="security_settings_face_enroll_introduction_message" product="device">Use your face to unlock your device, authorize purchases, or sign in to apps.</string>
|
||||
<!-- Subtitle shown on the face enrollment introduction screen with in-app authentication. [CHAR LIMIT=NONE] -->
|
||||
<string name="security_settings_face_enroll_introduction_message_class3" product="default">Use your face to unlock your phone or for authentication in apps, like when you sign in to apps or approve a purchase.</string>
|
||||
<string name="security_settings_face_enroll_introduction_message_class3" product="default">Use your face to unlock your phone or for authentication in apps, like when you sign in to apps or approve a purchase</string>
|
||||
<!-- Subtitle shown on the face enrollment introduction screen with in-app authentication. [CHAR LIMIT=NONE] -->
|
||||
<string name="security_settings_face_enroll_introduction_message_class3" product="tablet">Use your face to unlock your tablet or for authentication in apps, like when you sign in to apps or approve a purchase.</string>
|
||||
<string name="security_settings_face_enroll_introduction_message_class3" product="tablet">Use your face to unlock your tablet or for authentication in apps, like when you sign in to apps or approve a purchase</string>
|
||||
<!-- Subtitle shown on the face enrollment introduction screen with in-app authentication. [CHAR LIMIT=NONE] -->
|
||||
<string name="security_settings_face_enroll_introduction_message_class3" product="device">Use your face to unlock your device or for authentication in apps, like when you sign in to apps or approve a purchase.</string>
|
||||
<string name="security_settings_face_enroll_introduction_message_class3" product="device">Use your face to unlock your device or for authentication in apps, like when you sign in to apps or approve a purchase</string>
|
||||
<!-- Introduction detail message shown in face enrollment dialog when asking for parental consent [CHAR LIMIT=NONE]-->
|
||||
<string name="security_settings_face_enroll_introduction_consent_message_0" product="default">Allow your child to use their face to unlock their phone</string>
|
||||
<!-- Introduction detail message shown in face enrollment dialog when asking for parental consent [CHAR LIMIT=NONE]-->
|
||||
|
||||
@@ -469,8 +469,8 @@
|
||||
<dimen name="screen_flash_color_button_frame_size">48dp</dimen>
|
||||
<dimen name="screen_flash_color_button_outer_circle_size">48dp</dimen>
|
||||
<dimen name="screen_flash_color_button_outer_circle_stroke_width">2dp</dimen>
|
||||
<dimen name="screen_flash_color_button_inner_circle_size">42dp</dimen>
|
||||
<dimen name="screen_flash_color_button_inner_circle_padding">3dp</dimen>
|
||||
<dimen name="screen_flash_color_button_inner_circle_size">38dp</dimen>
|
||||
<dimen name="screen_flash_color_button_inner_circle_padding">5dp</dimen>
|
||||
<dimen name="screen_flash_color_button_inner_circle_stroke">1dp</dimen>
|
||||
|
||||
<!-- An arbitrarily large number to make the max size fit the parent -->
|
||||
|
||||
@@ -13385,10 +13385,8 @@
|
||||
<string name="audio_streams_add_source_bad_code_state_summary">Check password and try again</string>
|
||||
<!-- The preference summary when add source response results in general failure [CHAR LIMIT=NONE] -->
|
||||
<string name="audio_streams_add_source_failed_state_summary">Can\u0027t connect. Try again.</string>
|
||||
<!-- The preference summary when waiting for add source response [CHAR LIMIT=NONE] -->
|
||||
<string name="audio_streams_add_source_wait_for_response_summary">Connecting\u2026</string>
|
||||
<!-- The preference summary when waiting for sync [CHAR LIMIT=NONE] -->
|
||||
<string name="audio_streams_wait_for_sync_state_summary">Scanning\u2026</string>
|
||||
<!-- The preference summary when connecting [CHAR LIMIT=NONE] -->
|
||||
<string name="audio_streams_connecting_summary">Connecting\u2026</string>
|
||||
<!-- Le audio streams audio lost dialog title [CHAR LIMIT=NONE] -->
|
||||
<string name="audio_streams_dialog_stream_is_not_available">Audio stream isn\u0027t available</string>
|
||||
<!-- Le audio streams audio lost dialog subtitle [CHAR LIMIT=NONE] -->
|
||||
@@ -13408,7 +13406,7 @@
|
||||
<!-- Le audio streams confirm dialog default device [CHAR LIMIT=NONE] -->
|
||||
<string name="audio_streams_dialog_default_device">connected compatible headphones</string>
|
||||
<!-- Le audio streams activity title [CHAR LIMIT=NONE] -->
|
||||
<string name="audio_streams_activity_title">Broadcasts</string>
|
||||
<string name="audio_streams_activity_title">Audio streams</string>
|
||||
<!-- Le audio streams no password summary [CHAR LIMIT=NONE] -->
|
||||
<string name="audio_streams_no_password_summary">No password</string>
|
||||
<!-- Le audio streams failure dialog subtitle [CHAR LIMIT=NONE] -->
|
||||
|
||||
@@ -67,16 +67,12 @@ public class MagnificationAlwaysOnPreferenceController extends
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
if (Flags.hideMagnificationAlwaysOnToggleWhenWindowModeOnly()) {
|
||||
MagnificationCapabilities.registerObserver(mContext, mContentObserver);
|
||||
}
|
||||
MagnificationCapabilities.registerObserver(mContext, mContentObserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
if (Flags.hideMagnificationAlwaysOnToggleWhenWindowModeOnly()) {
|
||||
MagnificationCapabilities.unregisterObserver(mContext, mContentObserver);
|
||||
}
|
||||
MagnificationCapabilities.unregisterObserver(mContext, mContentObserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -111,10 +107,6 @@ public class MagnificationAlwaysOnPreferenceController extends
|
||||
|
||||
@Override
|
||||
public CharSequence getSummary() {
|
||||
if (!Flags.hideMagnificationAlwaysOnToggleWhenWindowModeOnly()) {
|
||||
return super.getSummary();
|
||||
}
|
||||
|
||||
@StringRes int resId = mPreference.isEnabled()
|
||||
? R.string.accessibility_screen_magnification_always_on_summary
|
||||
: R.string.accessibility_screen_magnification_always_on_unavailable_summary;
|
||||
@@ -124,9 +116,6 @@ public class MagnificationAlwaysOnPreferenceController extends
|
||||
@Override
|
||||
public void updateState(Preference preference) {
|
||||
super.updateState(preference);
|
||||
if (!Flags.hideMagnificationAlwaysOnToggleWhenWindowModeOnly()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (preference == null) {
|
||||
return;
|
||||
|
||||
@@ -168,7 +168,7 @@ public class PrimaryProviderPreference extends GearPreference {
|
||||
mButtonFrameView.setPadding(
|
||||
paddingLeft,
|
||||
mButtonFrameView.getPaddingTop(),
|
||||
mButtonFrameView.getPaddingRight(),
|
||||
paddingLeft,
|
||||
mButtonFrameView.getPaddingBottom());
|
||||
}
|
||||
|
||||
|
||||
@@ -170,22 +170,25 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
|
||||
infoMessageRequireEyes.setText(getInfoMessageRequireEyes());
|
||||
}
|
||||
|
||||
mFaceManager.addAuthenticatorsRegisteredCallback(
|
||||
new IFaceAuthenticatorsRegisteredCallback.Stub() {
|
||||
@Override
|
||||
public void onAllAuthenticatorsRegistered(
|
||||
@NonNull List<FaceSensorPropertiesInternal> sensors) {
|
||||
if (sensors.isEmpty()) {
|
||||
Log.e(TAG, "No sensors");
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isFaceStrong = sensors.get(0).sensorStrength
|
||||
== SensorProperties.STRENGTH_STRONG;
|
||||
mIsFaceStrong = isFaceStrong;
|
||||
onFaceStrengthChanged();
|
||||
}
|
||||
});
|
||||
if (mFaceManager != null) {
|
||||
mFaceManager.addAuthenticatorsRegisteredCallback(
|
||||
new IFaceAuthenticatorsRegisteredCallback.Stub() {
|
||||
@Override
|
||||
public void onAllAuthenticatorsRegistered(
|
||||
@NonNull List<FaceSensorPropertiesInternal> sensors) {
|
||||
if (sensors.isEmpty()) {
|
||||
Log.e(TAG, "No sensors");
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isFaceStrong = sensors.get(0).sensorStrength
|
||||
== SensorProperties.STRENGTH_STRONG;
|
||||
mIsFaceStrong = isFaceStrong;
|
||||
onFaceStrengthChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// This path is an entry point for SetNewPasswordController, e.g.
|
||||
// adb shell am start -a android.app.action.SET_NEW_PASSWORD
|
||||
|
||||
@@ -28,7 +28,7 @@ import com.android.settingslib.utils.ThreadUtils;
|
||||
class AddSourceWaitForResponseState extends AudioStreamStateHandler {
|
||||
@VisibleForTesting
|
||||
static final int AUDIO_STREAM_ADD_SOURCE_WAIT_FOR_RESPONSE_STATE_SUMMARY =
|
||||
R.string.audio_streams_add_source_wait_for_response_summary;
|
||||
R.string.audio_streams_connecting_summary;
|
||||
|
||||
@VisibleForTesting static final int ADD_SOURCE_WAIT_FOR_RESPONSE_TIMEOUT_MILLIS = 20000;
|
||||
|
||||
|
||||
@@ -19,13 +19,15 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.dashboard.DashboardFragment;
|
||||
|
||||
public class AudioStreamDetailsFragment extends DashboardFragment {
|
||||
static final String BROADCAST_NAME_ARG = "broadcast_name";
|
||||
static final String BROADCAST_ID_ARG = "broadcast_id";
|
||||
private static final String TAG = "AudioStreamDetailsFragment";
|
||||
@VisibleForTesting static final String TAG = "AudioStreamDetailsFragment";
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
import com.android.settings.R;
|
||||
@@ -60,7 +61,8 @@ class AudioStreamPreference extends TwoTargetPreference {
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
private AudioStreamPreference(Context context, @Nullable AttributeSet attrs) {
|
||||
@VisibleForTesting
|
||||
AudioStreamPreference(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setIcon(com.android.settingslib.R.drawable.ic_bt_le_audio_sharing);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ import com.android.settingslib.utils.ThreadUtils;
|
||||
class WaitForSyncState extends AudioStreamStateHandler {
|
||||
@VisibleForTesting
|
||||
static final int AUDIO_STREAM_WAIT_FOR_SYNC_STATE_SUMMARY =
|
||||
R.string.audio_streams_wait_for_sync_state_summary;
|
||||
R.string.audio_streams_connecting_summary;
|
||||
|
||||
@VisibleForTesting static final int WAIT_FOR_SYNC_TIMEOUT_MILLIS = 15000;
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.deviceinfo.simstatus
|
||||
|
||||
import android.content.Context
|
||||
import android.telephony.SubscriptionManager
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.android.settings.network.telephony.SimSlotRepository
|
||||
import com.android.settings.network.telephony.ims.ImsMmTelRepository
|
||||
import com.android.settings.network.telephony.ims.ImsMmTelRepositoryImpl
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class ImsRegistrationStateController @JvmOverloads constructor(
|
||||
private val context: Context,
|
||||
private val simSlotRepository: SimSlotRepository = SimSlotRepository(context),
|
||||
private val imsMmTelRepositoryFactory: (subId: Int) -> ImsMmTelRepository = { subId ->
|
||||
ImsMmTelRepositoryImpl(context, subId)
|
||||
},
|
||||
) {
|
||||
fun collectImsRegistered(
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
simSlotIndex: Int,
|
||||
action: (imsRegistered: Boolean) -> Unit,
|
||||
) {
|
||||
lifecycleOwner.lifecycleScope.launch {
|
||||
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
imsRegisteredFlow(simSlotIndex).collect(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun imsRegisteredFlow(simSlotIndex: Int): Flow<Boolean> =
|
||||
simSlotRepository.subIdInSimSlotFlow(simSlotIndex)
|
||||
.flatMapLatest { subId ->
|
||||
if (SubscriptionManager.isValidSubscriptionId(subId)) {
|
||||
imsMmTelRepositoryFactory(subId).imsRegisteredFlow()
|
||||
} else {
|
||||
flowOf(false)
|
||||
}
|
||||
}
|
||||
.conflate()
|
||||
.flowOn(Dispatchers.Default)
|
||||
}
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package com.android.settings.deviceinfo.simstatus;
|
||||
|
||||
import static androidx.lifecycle.Lifecycle.Event;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
@@ -30,7 +28,6 @@ import android.content.res.Resources;
|
||||
import android.os.IBinder;
|
||||
import android.os.PersistableBundle;
|
||||
import android.os.RemoteException;
|
||||
import android.telephony.AccessNetworkConstants;
|
||||
import android.telephony.Annotation;
|
||||
import android.telephony.CarrierConfigManager;
|
||||
import android.telephony.CellBroadcastIntents;
|
||||
@@ -46,29 +43,28 @@ import android.telephony.TelephonyCallback;
|
||||
import android.telephony.TelephonyDisplayInfo;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.telephony.euicc.EuiccManager;
|
||||
import android.telephony.ims.ImsException;
|
||||
import android.telephony.ims.ImsMmTelManager;
|
||||
import android.telephony.ims.ImsReasonInfo;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.lifecycle.LifecycleObserver;
|
||||
import androidx.lifecycle.OnLifecycleEvent;
|
||||
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.network.SubscriptionUtil;
|
||||
import com.android.settingslib.Utils;
|
||||
import com.android.settingslib.core.lifecycle.Lifecycle;
|
||||
|
||||
import kotlin.Unit;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Controller for Sim Status information within the About Phone Settings page.
|
||||
*/
|
||||
public class SimStatusDialogController implements LifecycleObserver {
|
||||
public class SimStatusDialogController implements DefaultLifecycleObserver {
|
||||
|
||||
private final static String TAG = "SimStatusDialogCtrl";
|
||||
|
||||
@@ -110,26 +106,7 @@ public class SimStatusDialogController implements LifecycleObserver {
|
||||
new OnSubscriptionsChangedListener() {
|
||||
@Override
|
||||
public void onSubscriptionsChanged() {
|
||||
final int prevSubId = (mSubscriptionInfo != null)
|
||||
? mSubscriptionInfo.getSubscriptionId()
|
||||
: SubscriptionManager.INVALID_SUBSCRIPTION_ID;
|
||||
|
||||
mSubscriptionInfo = getPhoneSubscriptionInfo(mSlotIndex);
|
||||
|
||||
final int nextSubId = (mSubscriptionInfo != null)
|
||||
? mSubscriptionInfo.getSubscriptionId()
|
||||
: SubscriptionManager.INVALID_SUBSCRIPTION_ID;
|
||||
|
||||
if (prevSubId != nextSubId) {
|
||||
if (SubscriptionManager.isValidSubscriptionId(prevSubId)) {
|
||||
unregisterImsRegistrationCallback(prevSubId);
|
||||
}
|
||||
if (SubscriptionManager.isValidSubscriptionId(nextSubId)) {
|
||||
mTelephonyManager =
|
||||
getTelephonyManager().createForSubscriptionId(nextSubId);
|
||||
registerImsRegistrationCallback(nextSubId);
|
||||
}
|
||||
}
|
||||
updateSubscriptionStatus();
|
||||
}
|
||||
};
|
||||
@@ -269,8 +246,8 @@ public class SimStatusDialogController implements LifecycleObserver {
|
||||
/**
|
||||
* OnResume lifecycle event, resume listening for phone state or subscription changes.
|
||||
*/
|
||||
@OnLifecycleEvent(Event.ON_RESUME)
|
||||
public void onResume() {
|
||||
@Override
|
||||
public void onResume(@NonNull LifecycleOwner owner) {
|
||||
if (mSubscriptionInfo == null) {
|
||||
return;
|
||||
}
|
||||
@@ -280,7 +257,7 @@ public class SimStatusDialogController implements LifecycleObserver {
|
||||
.registerTelephonyCallback(mContext.getMainExecutor(), mTelephonyCallback);
|
||||
mSubscriptionManager.addOnSubscriptionsChangedListener(
|
||||
mContext.getMainExecutor(), mOnSubscriptionsChangedListener);
|
||||
registerImsRegistrationCallback(mSubscriptionInfo.getSubscriptionId());
|
||||
collectImsRegistered(owner);
|
||||
|
||||
if (mShowLatestAreaInfo) {
|
||||
updateAreaInfoText();
|
||||
@@ -295,8 +272,8 @@ public class SimStatusDialogController implements LifecycleObserver {
|
||||
/**
|
||||
* onPause lifecycle event, no longer listen for phone state or subscription changes.
|
||||
*/
|
||||
@OnLifecycleEvent(Event.ON_PAUSE)
|
||||
public void onPause() {
|
||||
@Override
|
||||
public void onPause(@NonNull LifecycleOwner owner) {
|
||||
if (mSubscriptionInfo == null) {
|
||||
if (mIsRegisteredListener) {
|
||||
mSubscriptionManager.removeOnSubscriptionsChangedListener(
|
||||
@@ -310,7 +287,6 @@ public class SimStatusDialogController implements LifecycleObserver {
|
||||
return;
|
||||
}
|
||||
|
||||
unregisterImsRegistrationCallback(mSubscriptionInfo.getSubscriptionId());
|
||||
mSubscriptionManager.removeOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
|
||||
getTelephonyManager().unregisterTelephonyCallback(mTelephonyCallback);
|
||||
|
||||
@@ -625,51 +601,22 @@ public class SimStatusDialogController implements LifecycleObserver {
|
||||
mDialog.removeSettingFromScreen(IMS_REGISTRATION_STATE_VALUE_ID);
|
||||
}
|
||||
|
||||
private ImsMmTelManager.RegistrationCallback mImsRegStateCallback =
|
||||
new ImsMmTelManager.RegistrationCallback() {
|
||||
@Override
|
||||
public void onRegistered(@AccessNetworkConstants.TransportType int imsTransportType) {
|
||||
mDialog.setText(IMS_REGISTRATION_STATE_VALUE_ID, mRes.getString(
|
||||
com.android.settingslib.R.string.ims_reg_status_registered));
|
||||
}
|
||||
@Override
|
||||
public void onRegistering(@AccessNetworkConstants.TransportType int imsTransportType) {
|
||||
mDialog.setText(IMS_REGISTRATION_STATE_VALUE_ID, mRes.getString(
|
||||
com.android.settingslib.R.string.ims_reg_status_not_registered));
|
||||
}
|
||||
@Override
|
||||
public void onUnregistered(@Nullable ImsReasonInfo info) {
|
||||
mDialog.setText(IMS_REGISTRATION_STATE_VALUE_ID, mRes.getString(
|
||||
com.android.settingslib.R.string.ims_reg_status_not_registered));
|
||||
}
|
||||
@Override
|
||||
public void onTechnologyChangeFailed(
|
||||
@AccessNetworkConstants.TransportType int imsTransportType,
|
||||
@Nullable ImsReasonInfo info) {
|
||||
mDialog.setText(IMS_REGISTRATION_STATE_VALUE_ID, mRes.getString(
|
||||
com.android.settingslib.R.string.ims_reg_status_not_registered));
|
||||
}
|
||||
};
|
||||
|
||||
private void registerImsRegistrationCallback(int subId) {
|
||||
private void collectImsRegistered(@NonNull LifecycleOwner owner) {
|
||||
if (!isImsRegistrationStateShowUp()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final ImsMmTelManager imsMmTelMgr = ImsMmTelManager.createForSubscriptionId(subId);
|
||||
imsMmTelMgr.registerImsRegistrationCallback(mDialog.getContext().getMainExecutor(),
|
||||
mImsRegStateCallback);
|
||||
} catch (ImsException exception) {
|
||||
Log.w(TAG, "fail to register IMS status for subId=" + subId, exception);
|
||||
}
|
||||
}
|
||||
|
||||
private void unregisterImsRegistrationCallback(int subId) {
|
||||
if (!isImsRegistrationStateShowUp()) {
|
||||
return;
|
||||
}
|
||||
final ImsMmTelManager imsMmTelMgr = ImsMmTelManager.createForSubscriptionId(subId);
|
||||
imsMmTelMgr.unregisterImsRegistrationCallback(mImsRegStateCallback);
|
||||
new ImsRegistrationStateController(mContext).collectImsRegistered(
|
||||
owner, mSlotIndex, (Boolean imsRegistered) -> {
|
||||
if (imsRegistered) {
|
||||
mDialog.setText(IMS_REGISTRATION_STATE_VALUE_ID, mRes.getString(
|
||||
com.android.settingslib.R.string.ims_reg_status_registered));
|
||||
} else {
|
||||
mDialog.setText(IMS_REGISTRATION_STATE_VALUE_ID, mRes.getString(
|
||||
com.android.settingslib.R.string.ims_reg_status_not_registered));
|
||||
}
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private SubscriptionInfo getPhoneSubscriptionInfo(int slotId) {
|
||||
|
||||
@@ -455,7 +455,7 @@ public class SettingsHomepageActivity extends FragmentActivity implements
|
||||
}
|
||||
|
||||
private void updateHomepageBackground() {
|
||||
if (!mIsEmbeddingActivityEnabled) {
|
||||
if (!Flags.homepageRevamp() && !mIsEmbeddingActivityEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.network.telephony
|
||||
|
||||
import android.content.Context
|
||||
import android.telephony.SubscriptionManager
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
class SimSlotRepository(private val context: Context) {
|
||||
private val subscriptionManager = context.requireSubscriptionManager()
|
||||
|
||||
fun subIdInSimSlotFlow(simSlotIndex: Int) =
|
||||
context.subscriptionsChangedFlow()
|
||||
.map {
|
||||
subscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(simSlotIndex)
|
||||
?.subscriptionId
|
||||
?: SubscriptionManager.INVALID_SUBSCRIPTION_ID
|
||||
}
|
||||
.conflate()
|
||||
.onEach { Log.d(TAG, "sub id in sim slot $simSlotIndex: $it") }
|
||||
.flowOn(Dispatchers.Default)
|
||||
|
||||
private companion object {
|
||||
private const val TAG = "SimSlotRepository"
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,10 @@ import android.telephony.AccessNetworkConstants
|
||||
import android.telephony.ims.ImsManager
|
||||
import android.telephony.ims.ImsMmTelManager
|
||||
import android.telephony.ims.ImsMmTelManager.WiFiCallingMode
|
||||
import android.telephony.ims.ImsReasonInfo
|
||||
import android.telephony.ims.ImsRegistrationAttributes
|
||||
import android.telephony.ims.ImsStateCallback
|
||||
import android.telephony.ims.RegistrationManager
|
||||
import android.telephony.ims.feature.MmTelFeature
|
||||
import android.util.Log
|
||||
import kotlin.coroutines.resume
|
||||
@@ -39,7 +42,11 @@ import kotlinx.coroutines.withContext
|
||||
interface ImsMmTelRepository {
|
||||
@WiFiCallingMode
|
||||
fun getWiFiCallingMode(useRoamingMode: Boolean): Int
|
||||
|
||||
fun imsRegisteredFlow(): Flow<Boolean>
|
||||
|
||||
fun imsReadyFlow(): Flow<Boolean>
|
||||
|
||||
suspend fun isSupported(
|
||||
@MmTelFeature.MmTelCapabilities.MmTelCapability capability: Int,
|
||||
@AccessNetworkConstants.TransportType transportType: Int,
|
||||
@@ -64,6 +71,36 @@ class ImsMmTelRepositoryImpl(
|
||||
ImsMmTelManager.WIFI_MODE_UNKNOWN
|
||||
}
|
||||
|
||||
override fun imsRegisteredFlow(): Flow<Boolean> = callbackFlow {
|
||||
val callback = object : RegistrationManager.RegistrationCallback() {
|
||||
override fun onRegistered(attributes: ImsRegistrationAttributes) {
|
||||
Log.d(TAG, "[$subId] IMS onRegistered")
|
||||
trySend(true)
|
||||
}
|
||||
|
||||
override fun onRegistering(imsTransportType: Int) {
|
||||
Log.d(TAG, "[$subId] IMS onRegistering")
|
||||
trySend(false)
|
||||
}
|
||||
|
||||
override fun onTechnologyChangeFailed(imsTransportType: Int, info: ImsReasonInfo) {
|
||||
Log.d(TAG, "[$subId] IMS onTechnologyChangeFailed")
|
||||
trySend(false)
|
||||
}
|
||||
|
||||
override fun onUnregistered(info: ImsReasonInfo) {
|
||||
Log.d(TAG, "[$subId] IMS onUnregistered")
|
||||
trySend(false)
|
||||
}
|
||||
}
|
||||
|
||||
imsMmTelManager.registerImsRegistrationCallback(Dispatchers.Default.asExecutor(), callback)
|
||||
|
||||
awaitClose { imsMmTelManager.unregisterImsRegistrationCallback(callback) }
|
||||
}.catch { e ->
|
||||
Log.w(TAG, "[$subId] error while imsRegisteredFlow", e)
|
||||
}.conflate().flowOn(Dispatchers.Default)
|
||||
|
||||
override fun imsReadyFlow(): Flow<Boolean> = callbackFlow {
|
||||
val callback = object : ImsStateCallback() {
|
||||
override fun onAvailable() {
|
||||
|
||||
50
src/com/android/settings/notification/modes/FutureUtil.java
Normal file
50
src/com/android/settings/notification/modes/FutureUtil.java
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.notification.modes;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
class FutureUtil {
|
||||
|
||||
private static final String TAG = "ZenFutureUtil";
|
||||
|
||||
static <V> void whenDone(ListenableFuture<V> future, Consumer<V> consumer, Executor executor) {
|
||||
whenDone(future, consumer, executor, "Error in future");
|
||||
}
|
||||
|
||||
static <V> void whenDone(ListenableFuture<V> future, Consumer<V> consumer, Executor executor,
|
||||
String errorLogMessage, Object... errorLogMessageArgs) {
|
||||
Futures.addCallback(future, new FutureCallback<V>() {
|
||||
@Override
|
||||
public void onSuccess(V v) {
|
||||
consumer.accept(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable throwable) {
|
||||
Log.e(TAG, String.format(errorLogMessage, errorLogMessageArgs), throwable);
|
||||
}
|
||||
}, executor);
|
||||
}
|
||||
}
|
||||
159
src/com/android/settings/notification/modes/IconLoader.java
Normal file
159
src/com/android/settings/notification/modes/IconLoader.java
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.notification.modes;
|
||||
|
||||
import static com.google.common.util.concurrent.Futures.immediateFuture;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.app.AutomaticZenRule;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.AdaptiveIconDrawable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.InsetDrawable;
|
||||
import android.service.notification.SystemZenRules;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.LruCache;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
|
||||
import com.android.settings.R;
|
||||
|
||||
import com.google.common.util.concurrent.FluentFuture;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
class IconLoader {
|
||||
|
||||
private static final String TAG = "ZenIconLoader";
|
||||
|
||||
private static final Drawable MISSING = new ColorDrawable();
|
||||
|
||||
@Nullable // Until first usage
|
||||
private static IconLoader sInstance;
|
||||
|
||||
private final Context mContext;
|
||||
private final LruCache<String, Drawable> mCache;
|
||||
private final ListeningExecutorService mBackgroundExecutor;
|
||||
|
||||
static IconLoader getInstance(Context context) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new IconLoader(context);
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
private IconLoader(Context context) {
|
||||
this(context, Executors.newFixedThreadPool(4));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
IconLoader(Context context, ExecutorService backgroundExecutor) {
|
||||
mContext = context.getApplicationContext();
|
||||
mCache = new LruCache<>(50);
|
||||
mBackgroundExecutor =
|
||||
MoreExecutors.listeningDecorator(backgroundExecutor);
|
||||
}
|
||||
|
||||
Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
ListenableFuture<Drawable> getIcon(@NonNull AutomaticZenRule rule) {
|
||||
if (rule.getIconResId() == 0) {
|
||||
return Futures.immediateFuture(getFallbackIcon(rule.getType()));
|
||||
}
|
||||
|
||||
return FluentFuture.from(loadIcon(rule.getPackageName(), rule.getIconResId()))
|
||||
.transform(icon ->
|
||||
icon != null ? icon : getFallbackIcon(rule.getType()),
|
||||
MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private ListenableFuture</* @Nullable */ Drawable> loadIcon(String pkg, int iconResId) {
|
||||
String cacheKey = pkg + ":" + iconResId;
|
||||
synchronized (mCache) {
|
||||
Drawable cachedValue = mCache.get(cacheKey);
|
||||
if (cachedValue != null) {
|
||||
return immediateFuture(cachedValue != MISSING ? cachedValue : null);
|
||||
}
|
||||
}
|
||||
|
||||
return FluentFuture.from(mBackgroundExecutor.submit(() -> {
|
||||
if (TextUtils.isEmpty(pkg) || SystemZenRules.PACKAGE_ANDROID.equals(pkg)) {
|
||||
return mContext.getDrawable(iconResId);
|
||||
} else {
|
||||
Context appContext = mContext.createPackageContext(pkg, 0);
|
||||
Drawable appDrawable = AppCompatResources.getDrawable(appContext, iconResId);
|
||||
return getMonochromeIconIfPresent(appDrawable);
|
||||
}
|
||||
})).catching(Exception.class, ex -> {
|
||||
// If we cannot resolve the icon, then store MISSING in the cache below, so
|
||||
// we don't try again.
|
||||
Log.e(TAG, "Error while loading icon " + cacheKey, ex);
|
||||
return null;
|
||||
}, MoreExecutors.directExecutor()).transform(drawable -> {
|
||||
synchronized (mCache) {
|
||||
mCache.put(cacheKey, drawable != null ? drawable : MISSING);
|
||||
}
|
||||
return drawable;
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
private Drawable getFallbackIcon(int ruleType) {
|
||||
int iconResIdFromType = switch (ruleType) {
|
||||
// TODO: b/333528437 - continue replacing with proper default icons
|
||||
case AutomaticZenRule.TYPE_UNKNOWN -> R.drawable.ic_do_not_disturb_on_24dp;
|
||||
case AutomaticZenRule.TYPE_OTHER -> R.drawable.ic_do_not_disturb_on_24dp;
|
||||
case AutomaticZenRule.TYPE_SCHEDULE_TIME -> R.drawable.ic_modes_time;
|
||||
case AutomaticZenRule.TYPE_SCHEDULE_CALENDAR -> R.drawable.ic_modes_event;
|
||||
case AutomaticZenRule.TYPE_BEDTIME -> R.drawable.ic_do_not_disturb_on_24dp;
|
||||
case AutomaticZenRule.TYPE_DRIVING -> R.drawable.ic_do_not_disturb_on_24dp;
|
||||
case AutomaticZenRule.TYPE_IMMERSIVE -> R.drawable.ic_do_not_disturb_on_24dp;
|
||||
case AutomaticZenRule.TYPE_THEATER -> R.drawable.ic_do_not_disturb_on_24dp;
|
||||
case AutomaticZenRule.TYPE_MANAGED -> R.drawable.ic_do_not_disturb_on_24dp;
|
||||
default -> R.drawable.ic_do_not_disturb_on_24dp;
|
||||
};
|
||||
return requireNonNull(mContext.getDrawable(iconResIdFromType));
|
||||
}
|
||||
|
||||
private static Drawable getMonochromeIconIfPresent(Drawable icon) {
|
||||
// For created rules, the app should've provided a monochrome Drawable. However, implicit
|
||||
// rules have the app's icon, which is not -- but might have a monochrome layer. Thus
|
||||
// we choose it, if present.
|
||||
if (icon instanceof AdaptiveIconDrawable adaptiveIcon) {
|
||||
if (adaptiveIcon.getMonochrome() != null) {
|
||||
// Wrap with negative inset => scale icon (inspired from BaseIconFactory)
|
||||
return new InsetDrawable(adaptiveIcon.getMonochrome(),
|
||||
-2.0f * AdaptiveIconDrawable.getExtraInsetFraction());
|
||||
}
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
@@ -25,15 +25,12 @@ import android.annotation.SuppressLint;
|
||||
import android.app.AutomaticZenRule;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.service.notification.SystemZenRules;
|
||||
import android.service.notification.ZenPolicy;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
|
||||
import com.android.settings.R;
|
||||
|
||||
@@ -94,8 +91,6 @@ class ZenMode {
|
||||
private final boolean mIsActive;
|
||||
private final boolean mIsManualDnd;
|
||||
|
||||
// private ZenPolicy mPreviousPolicy;
|
||||
|
||||
ZenMode(String id, AutomaticZenRule rule, boolean isActive) {
|
||||
this(id, rule, isActive, false);
|
||||
}
|
||||
@@ -122,49 +117,14 @@ class ZenMode {
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public ListenableFuture<Drawable> getIcon(@NonNull Context context) {
|
||||
// TODO: b/333528586 - Load the icons asynchronously, and cache them
|
||||
public ListenableFuture<Drawable> getIcon(@NonNull IconLoader iconLoader) {
|
||||
Context context = iconLoader.getContext();
|
||||
if (mIsManualDnd) {
|
||||
return Futures.immediateFuture(
|
||||
requireNonNull(context.getDrawable(R.drawable.ic_do_not_disturb_on_24dp)));
|
||||
return Futures.immediateFuture(requireNonNull(
|
||||
context.getDrawable(R.drawable.ic_do_not_disturb_on_24dp)));
|
||||
}
|
||||
|
||||
int iconResId = mRule.getIconResId();
|
||||
Drawable customIcon = null;
|
||||
if (iconResId != 0) {
|
||||
if (SystemZenRules.PACKAGE_ANDROID.equals(mRule.getPackageName())) {
|
||||
customIcon = context.getDrawable(mRule.getIconResId());
|
||||
} else {
|
||||
try {
|
||||
Context appContext = context.createPackageContext(mRule.getPackageName(), 0);
|
||||
customIcon = AppCompatResources.getDrawable(appContext, mRule.getIconResId());
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.wtf(TAG,
|
||||
"Package " + mRule.getPackageName() + " used in rule " + mId
|
||||
+ " not found?", e);
|
||||
// Continue down to use a default icon.
|
||||
}
|
||||
}
|
||||
}
|
||||
if (customIcon != null) {
|
||||
return Futures.immediateFuture(customIcon);
|
||||
}
|
||||
|
||||
// Derive a default icon from the rule type.
|
||||
// TODO: b/333528437 - Use correct icons
|
||||
int iconResIdFromType = switch (mRule.getType()) {
|
||||
case AutomaticZenRule.TYPE_UNKNOWN -> R.drawable.ic_do_not_disturb_on_24dp;
|
||||
case AutomaticZenRule.TYPE_OTHER -> R.drawable.ic_do_not_disturb_on_24dp;
|
||||
case AutomaticZenRule.TYPE_SCHEDULE_TIME -> R.drawable.ic_modes_time;
|
||||
case AutomaticZenRule.TYPE_SCHEDULE_CALENDAR -> R.drawable.ic_modes_event;
|
||||
case AutomaticZenRule.TYPE_BEDTIME -> R.drawable.ic_do_not_disturb_on_24dp;
|
||||
case AutomaticZenRule.TYPE_DRIVING -> R.drawable.ic_do_not_disturb_on_24dp;
|
||||
case AutomaticZenRule.TYPE_IMMERSIVE -> R.drawable.ic_do_not_disturb_on_24dp;
|
||||
case AutomaticZenRule.TYPE_THEATER -> R.drawable.ic_do_not_disturb_on_24dp;
|
||||
case AutomaticZenRule.TYPE_MANAGED -> R.drawable.ic_do_not_disturb_on_24dp;
|
||||
default -> R.drawable.ic_do_not_disturb_on_24dp;
|
||||
};
|
||||
return Futures.immediateFuture(requireNonNull(context.getDrawable(iconResIdFromType)));
|
||||
return iconLoader.getIcon(mRule);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
||||
@@ -17,7 +17,6 @@ package com.android.settings.notification.modes;
|
||||
|
||||
import android.app.Flags;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -28,9 +27,7 @@ import com.android.settings.dashboard.DashboardFragment;
|
||||
import com.android.settings.widget.EntityHeaderController;
|
||||
import com.android.settingslib.widget.LayoutPreference;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ZenModeHeaderController extends AbstractZenModePreferenceController {
|
||||
class ZenModeHeaderController extends AbstractZenModePreferenceController {
|
||||
|
||||
private final DashboardFragment mFragment;
|
||||
private EntityHeaderController mHeaderController;
|
||||
@@ -51,7 +48,8 @@ public class ZenModeHeaderController extends AbstractZenModePreferenceController
|
||||
|
||||
@Override
|
||||
public void updateState(Preference preference) {
|
||||
if (getAZR() == null || mFragment == null) {
|
||||
ZenMode mode = getMode();
|
||||
if (mode == null || mFragment == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -62,14 +60,12 @@ public class ZenModeHeaderController extends AbstractZenModePreferenceController
|
||||
mFragment,
|
||||
pref.findViewById(R.id.entity_header));
|
||||
}
|
||||
Drawable icon = null;
|
||||
try {
|
||||
icon = getMode().getIcon(mContext).get(200, TimeUnit.MILLISECONDS);
|
||||
} catch (Exception e) {
|
||||
// no icon
|
||||
}
|
||||
mHeaderController.setIcon(icon)
|
||||
.setLabel(getAZR().getName())
|
||||
.done(false /* rebindActions */);
|
||||
|
||||
FutureUtil.whenDone(
|
||||
mode.getIcon(IconLoader.getInstance(mContext)),
|
||||
icon -> mHeaderController.setIcon(icon)
|
||||
.setLabel(mode.getRule().getName())
|
||||
.done(false /* rebindActions */),
|
||||
mContext.getMainExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,15 +25,11 @@ import com.android.settings.core.SubSettingLauncher;
|
||||
import com.android.settings.notification.zen.ZenModeSettings;
|
||||
import com.android.settingslib.RestrictedPreference;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* Preference representing a single mode item on the modes aggregator page. Clicking on this
|
||||
* preference leads to an individual mode's configuration page.
|
||||
*/
|
||||
public class ZenModeListPreference extends RestrictedPreference {
|
||||
class ZenModeListPreference extends RestrictedPreference {
|
||||
final Context mContext;
|
||||
ZenMode mZenMode;
|
||||
|
||||
@@ -68,10 +64,10 @@ public class ZenModeListPreference extends RestrictedPreference {
|
||||
mZenMode = zenMode;
|
||||
setTitle(mZenMode.getRule().getName());
|
||||
setSummary(mZenMode.getRule().getTriggerDescription());
|
||||
try {
|
||||
setIcon(mZenMode.getIcon(mContext).get(200, TimeUnit.MILLISECONDS));
|
||||
} catch (Exception e) {
|
||||
// no icon
|
||||
}
|
||||
|
||||
FutureUtil.whenDone(
|
||||
mZenMode.getIcon(IconLoader.getInstance(mContext)),
|
||||
icon -> setIcon(icon),
|
||||
mContext.getMainExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@ import com.android.settings.R;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -104,7 +103,6 @@ class ZenModesBackend {
|
||||
ZenMode getMode(String id) {
|
||||
ZenModeConfig currentConfig = mNotificationManager.getZenModeConfig();
|
||||
if (ZenMode.MANUAL_DND_MODE_ID.equals(id)) {
|
||||
// Regardless of its contents, non-null manualRule means that manual rule is active.
|
||||
return getManualDndMode(currentConfig);
|
||||
} else {
|
||||
AutomaticZenRule rule = mNotificationManager.getAutomaticZenRule(id);
|
||||
@@ -177,8 +175,9 @@ class ZenModesBackend {
|
||||
.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY)
|
||||
.build();
|
||||
|
||||
// Regardless of its contents, non-null manualRule means that manual rule is active.
|
||||
return ZenMode.manualDndMode(manualDndRule,
|
||||
config != null && config.manualRule != null); // isActive
|
||||
config != null && config.manualRule != null);
|
||||
}
|
||||
|
||||
private static boolean isRuleActive(String id, ZenModeConfig config) {
|
||||
|
||||
@@ -35,18 +35,14 @@ import com.android.settingslib.spa.widget.button.ActionButton
|
||||
import com.android.settingslib.spa.widget.dialog.AlertDialogButton
|
||||
import com.android.settingslib.spa.widget.dialog.AlertDialogPresenter
|
||||
import com.android.settingslib.spa.widget.dialog.rememberAlertDialogPresenter
|
||||
import com.android.settingslib.spaprivileged.model.app.hasFlag
|
||||
import com.android.settingslib.spaprivileged.model.app.isActiveAdmin
|
||||
import com.android.settingslib.spaprivileged.model.app.userId
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
|
||||
class AppForceStopButton(
|
||||
private val packageInfoPresenter: PackageInfoPresenter,
|
||||
private val appForceStopRepository: AppForceStopRepository =
|
||||
AppForceStopRepository(packageInfoPresenter),
|
||||
) {
|
||||
private val context = packageInfoPresenter.context
|
||||
private val appButtonRepository = AppButtonRepository(context)
|
||||
private val packageManager = context.packageManager
|
||||
|
||||
@Composable
|
||||
@@ -55,27 +51,11 @@ class AppForceStopButton(
|
||||
return ActionButton(
|
||||
text = stringResource(R.string.force_stop),
|
||||
imageVector = Icons.Outlined.Report,
|
||||
enabled = remember(app) {
|
||||
flow {
|
||||
emit(isForceStopButtonEnable(app))
|
||||
}.flowOn(Dispatchers.Default)
|
||||
}.collectAsStateWithLifecycle(false).value,
|
||||
enabled = remember(app) { appForceStopRepository.canForceStopFlow() }
|
||||
.collectAsStateWithLifecycle(false).value,
|
||||
) { onForceStopButtonClicked(app, dialogPresenter) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether a package can be force stopped.
|
||||
*/
|
||||
private fun isForceStopButtonEnable(app: ApplicationInfo): Boolean = when {
|
||||
// User can't force stop device admin.
|
||||
app.isActiveAdmin(context) -> false
|
||||
|
||||
appButtonRepository.isDisallowControl(app) -> false
|
||||
|
||||
// If the app isn't explicitly stopped, then always show the force stop button.
|
||||
else -> !app.hasFlag(ApplicationInfo.FLAG_STOPPED)
|
||||
}
|
||||
|
||||
private fun onForceStopButtonClicked(
|
||||
app: ApplicationInfo,
|
||||
dialogPresenter: AlertDialogPresenter,
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.net.Uri
|
||||
import android.os.UserHandle
|
||||
import android.util.Log
|
||||
import com.android.settingslib.spaprivileged.model.app.hasFlag
|
||||
import com.android.settingslib.spaprivileged.model.app.isActiveAdmin
|
||||
import com.android.settingslib.spaprivileged.model.app.userId
|
||||
import kotlin.coroutines.resume
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
|
||||
class AppForceStopRepository(
|
||||
private val packageInfoPresenter: PackageInfoPresenter,
|
||||
private val appButtonRepository: AppButtonRepository =
|
||||
AppButtonRepository(packageInfoPresenter.context),
|
||||
) {
|
||||
private val context = packageInfoPresenter.context
|
||||
|
||||
/**
|
||||
* Flow of whether a package can be force stopped.
|
||||
*/
|
||||
fun canForceStopFlow(): Flow<Boolean> = packageInfoPresenter.flow
|
||||
.map { packageInfo ->
|
||||
val app = packageInfo?.applicationInfo ?: return@map false
|
||||
canForceStop(app)
|
||||
}
|
||||
.conflate()
|
||||
.onEach { Log.d(TAG, "canForceStopFlow: $it") }
|
||||
.flowOn(Dispatchers.Default)
|
||||
|
||||
/**
|
||||
* Gets whether a package can be force stopped.
|
||||
*/
|
||||
private suspend fun canForceStop(app: ApplicationInfo): Boolean = when {
|
||||
// User can't force stop device admin.
|
||||
app.isActiveAdmin(context) -> false
|
||||
|
||||
appButtonRepository.isDisallowControl(app) -> false
|
||||
|
||||
// If the app isn't explicitly stopped, then always show the force stop button.
|
||||
!app.hasFlag(ApplicationInfo.FLAG_STOPPED) -> true
|
||||
|
||||
else -> queryAppRestart(app)
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries if app has restarted.
|
||||
*
|
||||
* @return true means app can be force stop again.
|
||||
*/
|
||||
private suspend fun queryAppRestart(app: ApplicationInfo): Boolean {
|
||||
val packageName = app.packageName
|
||||
val intent = Intent(
|
||||
Intent.ACTION_QUERY_PACKAGE_RESTART,
|
||||
Uri.fromParts("package", packageName, null)
|
||||
).apply {
|
||||
putExtra(Intent.EXTRA_PACKAGES, arrayOf(packageName))
|
||||
putExtra(Intent.EXTRA_UID, app.uid)
|
||||
putExtra(Intent.EXTRA_USER_HANDLE, app.userId)
|
||||
}
|
||||
Log.d(TAG, "Sending broadcast to query restart status for $packageName")
|
||||
|
||||
return suspendCancellableCoroutine { continuation ->
|
||||
val receiver: BroadcastReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val enabled = resultCode != Activity.RESULT_CANCELED
|
||||
Log.d(TAG, "Got broadcast response: Restart status for $packageName $enabled")
|
||||
continuation.resume(enabled)
|
||||
}
|
||||
}
|
||||
context.sendOrderedBroadcastAsUser(
|
||||
intent,
|
||||
UserHandle.CURRENT,
|
||||
Manifest.permission.HANDLE_QUERY_PACKAGE_RESTART,
|
||||
receiver,
|
||||
null,
|
||||
Activity.RESULT_CANCELED,
|
||||
null,
|
||||
null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val TAG = "AppForceStopRepository"
|
||||
}
|
||||
}
|
||||
@@ -28,9 +28,6 @@ import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Context;
|
||||
import android.platform.test.annotations.DisableFlags;
|
||||
import android.platform.test.annotations.EnableFlags;
|
||||
import android.platform.test.flag.junit.SetFlagsRule;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
@@ -39,7 +36,6 @@ import androidx.preference.SwitchPreference;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
@@ -49,8 +45,6 @@ import org.robolectric.shadows.ShadowContentResolver;
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class MagnificationAlwaysOnPreferenceControllerTest {
|
||||
|
||||
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
||||
|
||||
private static final String KEY_ALWAYS_ON =
|
||||
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED;
|
||||
|
||||
@@ -99,8 +93,7 @@ public class MagnificationAlwaysOnPreferenceControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_HIDE_MAGNIFICATION_ALWAYS_ON_TOGGLE_WHEN_WINDOW_MODE_ONLY)
|
||||
public void onResume_flagOn_verifyRegisterCapabilityObserver() {
|
||||
public void onResume_verifyRegisterCapabilityObserver() {
|
||||
mController.onResume();
|
||||
assertThat(mShadowContentResolver.getContentObservers(
|
||||
Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY)))
|
||||
@@ -108,8 +101,7 @@ public class MagnificationAlwaysOnPreferenceControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_HIDE_MAGNIFICATION_ALWAYS_ON_TOGGLE_WHEN_WINDOW_MODE_ONLY)
|
||||
public void onPause_flagOn_verifyUnregisterCapabilityObserver() {
|
||||
public void onPause_verifyUnregisterCapabilityObserver() {
|
||||
mController.onResume();
|
||||
mController.onPause();
|
||||
assertThat(mShadowContentResolver.getContentObservers(
|
||||
@@ -118,17 +110,7 @@ public class MagnificationAlwaysOnPreferenceControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisableFlags(Flags.FLAG_HIDE_MAGNIFICATION_ALWAYS_ON_TOGGLE_WHEN_WINDOW_MODE_ONLY)
|
||||
public void updateState_windowModeOnlyAndFlagOff_preferenceIsAvailable() {
|
||||
MagnificationCapabilities.setCapabilities(mContext, MagnificationMode.WINDOW);
|
||||
|
||||
mController.updateState(mSwitchPreference);
|
||||
assertThat(mSwitchPreference.isEnabled()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_HIDE_MAGNIFICATION_ALWAYS_ON_TOGGLE_WHEN_WINDOW_MODE_ONLY)
|
||||
public void updateState_windowModeOnlyAndFlagOn_preferenceBecomesUnavailable() {
|
||||
public void updateState_windowModeOnly_preferenceBecomesUnavailable() {
|
||||
MagnificationCapabilities.setCapabilities(mContext, MagnificationMode.WINDOW);
|
||||
|
||||
mController.updateState(mSwitchPreference);
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.connecteddevice.audiosharing.audiostreams;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class AudioStreamConfirmDialogActivityTest {
|
||||
private AudioStreamConfirmDialogActivity mActivity;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mActivity = Robolectric.buildActivity(AudioStreamConfirmDialogActivity.class).get();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isValidFragment_returnsTrue() {
|
||||
assertThat(mActivity.isValidFragment(AudioStreamConfirmDialog.class.getName())).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isValidFragment_returnsFalse() {
|
||||
assertThat(mActivity.isValidFragment("")).isFalse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.connecteddevice.audiosharing.audiostreams;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import com.android.settings.R;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class AudioStreamDetailsFragmentTest {
|
||||
private AudioStreamDetailsFragment mFragment;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mFragment = new AudioStreamDetailsFragment();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPreferenceScreenResId_returnsCorrectXml() {
|
||||
assertThat(mFragment.getPreferenceScreenResId())
|
||||
.isEqualTo(R.xml.bluetooth_le_audio_stream_details_fragment);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getLogTag_returnsCorrectTag() {
|
||||
assertThat(mFragment.getLogTag()).isEqualTo(AudioStreamDetailsFragment.TAG);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.connecteddevice.audiosharing.audiostreams;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.bluetooth.BluetoothLeAudioContentMetadata;
|
||||
import android.bluetooth.BluetoothLeBroadcastMetadata;
|
||||
import android.bluetooth.BluetoothLeBroadcastReceiveState;
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.preference.Preference.OnPreferenceClickListener;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class AudioStreamPreferenceTest {
|
||||
private static final int BROADCAST_ID = 1;
|
||||
private static final String BROADCAST_NAME = "broadcast_name";
|
||||
private static final String PROGRAM_NAME = "program_name";
|
||||
private static final int BROADCAST_RSSI = 1;
|
||||
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
private Context mContext;
|
||||
private AudioStreamPreference mPreference;
|
||||
@Mock private BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata;
|
||||
@Mock private BluetoothLeBroadcastReceiveState mBluetoothLeBroadcastReceiveState;
|
||||
@Mock private BluetoothLeAudioContentMetadata mBluetoothLeAudioContentMetadata;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mContext = RuntimeEnvironment.application;
|
||||
mPreference = new AudioStreamPreference(mContext, null);
|
||||
when(mBluetoothLeBroadcastMetadata.getBroadcastId()).thenReturn(BROADCAST_ID);
|
||||
when(mBluetoothLeBroadcastMetadata.getBroadcastName()).thenReturn(BROADCAST_NAME);
|
||||
when(mBluetoothLeBroadcastMetadata.getRssi()).thenReturn(BROADCAST_RSSI);
|
||||
when(mBluetoothLeBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID);
|
||||
when(mBluetoothLeBroadcastReceiveState.getSubgroupMetadata())
|
||||
.thenReturn(Collections.singletonList(mBluetoothLeAudioContentMetadata));
|
||||
when(mBluetoothLeAudioContentMetadata.getProgramInfo()).thenReturn(PROGRAM_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createNewPreference_shouldSetIcon() {
|
||||
assertThat(mPreference.getIcon()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBind_shouldHideDivider() {
|
||||
PreferenceViewHolder holder =
|
||||
PreferenceViewHolder.createInstanceForTests(
|
||||
LayoutInflater.from(mContext)
|
||||
.inflate(mPreference.getLayoutResource(), null));
|
||||
View divider =
|
||||
holder.findViewById(
|
||||
com.android.settingslib.widget.preference.twotarget.R.id
|
||||
.two_target_divider);
|
||||
assertThat(divider).isNotNull();
|
||||
|
||||
mPreference.onBindViewHolder(holder);
|
||||
|
||||
assertThat(divider.getVisibility()).isEqualTo(View.GONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setConnected_shouldUpdatePreferenceUI() {
|
||||
String summary = "Connected";
|
||||
OnPreferenceClickListener listener = mock(OnPreferenceClickListener.class);
|
||||
mPreference.setIsConnected(true, summary, listener);
|
||||
|
||||
assertThat(mPreference.getSummary()).isNotNull();
|
||||
assertThat(mPreference.getSummary().toString()).isEqualTo(summary);
|
||||
assertThat(mPreference.getOnPreferenceClickListener()).isEqualTo(listener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAudioStreamMetadata_shouldUpdateMetadata() {
|
||||
AudioStreamPreference p =
|
||||
AudioStreamPreference.fromMetadata(mContext, mBluetoothLeBroadcastMetadata);
|
||||
BluetoothLeBroadcastMetadata metadata = mock(BluetoothLeBroadcastMetadata.class);
|
||||
p.setAudioStreamMetadata(metadata);
|
||||
|
||||
assertThat(p.getAudioStreamMetadata()).isEqualTo(metadata);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAudioStreamState_shouldUpdateState() {
|
||||
AudioStreamPreference p =
|
||||
AudioStreamPreference.fromMetadata(mContext, mBluetoothLeBroadcastMetadata);
|
||||
AudioStreamState state = AudioStreamState.SOURCE_ADDED;
|
||||
p.setAudioStreamState(state);
|
||||
|
||||
assertThat(p.getAudioStreamState()).isEqualTo(state);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromMetadata_shouldReturnBroadcastInfo() {
|
||||
AudioStreamPreference p =
|
||||
AudioStreamPreference.fromMetadata(mContext, mBluetoothLeBroadcastMetadata);
|
||||
assertThat(p.getAudioStreamBroadcastId()).isEqualTo(BROADCAST_ID);
|
||||
assertThat(p.getAudioStreamBroadcastName()).isEqualTo(BROADCAST_NAME);
|
||||
assertThat(p.getAudioStreamRssi()).isEqualTo(BROADCAST_RSSI);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromReceiveState_shouldReturnBroadcastInfo() {
|
||||
AudioStreamPreference p =
|
||||
AudioStreamPreference.fromReceiveState(mContext, mBluetoothLeBroadcastReceiveState);
|
||||
assertThat(p.getAudioStreamBroadcastId()).isEqualTo(BROADCAST_ID);
|
||||
assertThat(p.getAudioStreamBroadcastName()).isEqualTo(PROGRAM_NAME);
|
||||
assertThat(p.getAudioStreamRssi()).isEqualTo(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldHideSecondTarget_connected() {
|
||||
mPreference.setIsConnected(true, "", null);
|
||||
assertThat(mPreference.shouldHideSecondTarget()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldHideSecondTarget_notEncrypted() {
|
||||
when(mBluetoothLeBroadcastMetadata.isEncrypted()).thenReturn(false);
|
||||
AudioStreamPreference p =
|
||||
AudioStreamPreference.fromMetadata(mContext, mBluetoothLeBroadcastMetadata);
|
||||
assertThat(p.shouldHideSecondTarget()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldShowSecondTarget_encrypted() {
|
||||
when(mBluetoothLeBroadcastMetadata.isEncrypted()).thenReturn(true);
|
||||
AudioStreamPreference p =
|
||||
AudioStreamPreference.fromMetadata(mContext, mBluetoothLeBroadcastMetadata);
|
||||
assertThat(p.shouldHideSecondTarget()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void secondTargetResId_shouldReturnLockLayoutId() {
|
||||
assertThat(mPreference.getSecondTargetResId()).isEqualTo(R.layout.preference_widget_lock);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.connecteddevice.audiosharing.audiostreams;
|
||||
|
||||
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class AudioStreamsActiveDeviceControllerTest {
|
||||
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
private AudioStreamsActiveDeviceController mController;
|
||||
@Mock private PreferenceScreen mScreen;
|
||||
@Mock private Preference mPreference;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
Context context = RuntimeEnvironment.application;
|
||||
mController =
|
||||
new AudioStreamsActiveDeviceController(
|
||||
context, AudioStreamsActiveDeviceController.KEY);
|
||||
when(mScreen.findPreference(anyString())).thenReturn(mPreference);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAvailabilityStatus() {
|
||||
assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onSummaryChanged_shouldSetPreferenceSummary() {
|
||||
String summary = "summary";
|
||||
mController.displayPreference(mScreen);
|
||||
mController.onSummaryChanged(summary);
|
||||
|
||||
verify(mPreference).setSummary(summary);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.connecteddevice.audiosharing.audiostreams;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows.ShadowAudioStreamsHelper;
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(
|
||||
shadows = {
|
||||
ShadowAudioStreamsHelper.class,
|
||||
})
|
||||
public class AudioStreamsActiveDeviceSummaryUpdaterTest {
|
||||
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
private static final String DEVICE_NAME = "device_name";
|
||||
@Spy private final Context mContext = ApplicationProvider.getApplicationContext();
|
||||
private final AudioStreamsActiveDeviceSummaryUpdater.OnSummaryChangeListener mFakeListener =
|
||||
summary -> mUpdatedSummary = summary;
|
||||
@Mock private CachedBluetoothDevice mCachedBluetoothDevice;
|
||||
@Mock private AudioStreamsHelper mAudioStreamsHelper;
|
||||
private @Nullable String mUpdatedSummary;
|
||||
private AudioStreamsActiveDeviceSummaryUpdater mUpdater;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
ShadowAudioStreamsHelper.setUseMock(mAudioStreamsHelper);
|
||||
ShadowAudioStreamsHelper.resetCachedBluetoothDevice();
|
||||
mUpdater = new AudioStreamsActiveDeviceSummaryUpdater(mContext, mFakeListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void register_summaryUpdated() {
|
||||
mUpdater.register(true);
|
||||
|
||||
assertThat(mUpdatedSummary).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onActiveDeviceChanged_notLeProfile_doNothing() {
|
||||
mUpdater.onActiveDeviceChanged(mCachedBluetoothDevice, 0);
|
||||
|
||||
assertThat(mUpdatedSummary).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onActiveDeviceChanged_leProfile_summaryUpdated() {
|
||||
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(
|
||||
mCachedBluetoothDevice);
|
||||
when(mCachedBluetoothDevice.getName()).thenReturn(DEVICE_NAME);
|
||||
mUpdater.onActiveDeviceChanged(mCachedBluetoothDevice, BluetoothProfile.LE_AUDIO);
|
||||
|
||||
assertThat(mUpdatedSummary).isEqualTo(DEVICE_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onActiveDeviceChanged_leProfile_noDevice_summaryUpdated() {
|
||||
mUpdater.onActiveDeviceChanged(mCachedBluetoothDevice, BluetoothProfile.LE_AUDIO);
|
||||
|
||||
assertThat(mUpdatedSummary)
|
||||
.isEqualTo(mContext.getString(R.string.audio_streams_dialog_no_le_device_title));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.notification.modes;
|
||||
|
||||
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.app.AutomaticZenRule;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.service.notification.ZenPolicy;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class IconLoaderTest {
|
||||
|
||||
private IconLoader mLoader;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mLoader = new IconLoader(RuntimeEnvironment.application,
|
||||
MoreExecutors.newDirectExecutorService());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIcon_systemOwnedRuleWithIcon_loads() throws Exception {
|
||||
AutomaticZenRule systemRule = newRuleBuilder()
|
||||
.setPackage("android")
|
||||
.setIconResId(android.R.drawable.ic_media_play)
|
||||
.build();
|
||||
|
||||
ListenableFuture<Drawable> loadFuture = mLoader.getIcon(systemRule);
|
||||
assertThat(loadFuture.isDone()).isTrue();
|
||||
assertThat(loadFuture.get()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIcon_ruleWithoutSpecificIcon_loadsFallback() throws Exception {
|
||||
AutomaticZenRule rule = newRuleBuilder()
|
||||
.setType(AutomaticZenRule.TYPE_DRIVING)
|
||||
.setPackage("com.blah")
|
||||
.build();
|
||||
|
||||
ListenableFuture<Drawable> loadFuture = mLoader.getIcon(rule);
|
||||
assertThat(loadFuture.isDone()).isTrue();
|
||||
assertThat(loadFuture.get()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIcon_ruleWithAppIconWithLoadFailure_loadsFallback() throws Exception {
|
||||
AutomaticZenRule rule = newRuleBuilder()
|
||||
.setType(AutomaticZenRule.TYPE_DRIVING)
|
||||
.setPackage("com.blah")
|
||||
.setIconResId(-123456)
|
||||
.build();
|
||||
|
||||
ListenableFuture<Drawable> loadFuture = mLoader.getIcon(rule);
|
||||
assertThat(loadFuture.get()).isNotNull();
|
||||
}
|
||||
|
||||
private static AutomaticZenRule.Builder newRuleBuilder() {
|
||||
return new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
|
||||
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
|
||||
.setZenPolicy(new ZenPolicy.Builder().build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.deviceinfo.simstatus
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.testing.TestLifecycleOwner
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.settings.network.telephony.SimSlotRepository
|
||||
import com.android.settings.network.telephony.ims.ImsMmTelRepository
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ImsRegistrationStateControllerTest {
|
||||
|
||||
private val context: Context = ApplicationProvider.getApplicationContext()
|
||||
|
||||
private val mockSimSlotRepository = mock<SimSlotRepository> {
|
||||
on { subIdInSimSlotFlow(SIM_SLOT_INDEX) } doReturn flowOf(SUB_ID)
|
||||
}
|
||||
|
||||
private val mockImsMmTelRepository = mock<ImsMmTelRepository> {
|
||||
on { imsRegisteredFlow() } doReturn flowOf(true)
|
||||
}
|
||||
|
||||
private val controller = ImsRegistrationStateController(
|
||||
context = context,
|
||||
simSlotRepository = mockSimSlotRepository,
|
||||
imsMmTelRepositoryFactory = { subId ->
|
||||
assertThat(subId).isEqualTo(SUB_ID)
|
||||
mockImsMmTelRepository
|
||||
},
|
||||
)
|
||||
|
||||
@Test
|
||||
fun collectImsRegistered() = runBlocking {
|
||||
var imsRegistered = false
|
||||
|
||||
controller.collectImsRegistered(TestLifecycleOwner(), SIM_SLOT_INDEX) {
|
||||
imsRegistered = it
|
||||
}
|
||||
delay(100)
|
||||
|
||||
assertThat(imsRegistered).isTrue()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val SIM_SLOT_INDEX = 0
|
||||
const val SUB_ID = 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.network.telephony
|
||||
|
||||
import android.content.Context
|
||||
import android.telephony.SubscriptionInfo
|
||||
import android.telephony.SubscriptionManager
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.doAnswer
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.spy
|
||||
import org.mockito.kotlin.stub
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class SimSlotRepositoryTest {
|
||||
|
||||
private val mockSubscriptionManager = mock<SubscriptionManager> {
|
||||
on { addOnSubscriptionsChangedListener(any(), any()) } doAnswer {
|
||||
val listener = it.arguments[1] as SubscriptionManager.OnSubscriptionsChangedListener
|
||||
listener.onSubscriptionsChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
|
||||
on { getSystemService(SubscriptionManager::class.java) } doReturn mockSubscriptionManager
|
||||
}
|
||||
|
||||
private val repository = SimSlotRepository(context)
|
||||
|
||||
@Test
|
||||
fun subIdInSimSlotFlow_valid() = runBlocking {
|
||||
mockSubscriptionManager.stub {
|
||||
on { getActiveSubscriptionInfoForSimSlotIndex(SIM_SLOT_INDEX) } doReturn
|
||||
SubscriptionInfo.Builder().setId(SUB_ID).build()
|
||||
}
|
||||
|
||||
val subId = repository.subIdInSimSlotFlow(SIM_SLOT_INDEX).firstWithTimeoutOrNull()
|
||||
|
||||
assertThat(subId).isEqualTo(SUB_ID)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun subIdInSimSlotFlow_invalid() = runBlocking {
|
||||
mockSubscriptionManager.stub {
|
||||
on { getActiveSubscriptionInfoForSimSlotIndex(SIM_SLOT_INDEX) } doReturn null
|
||||
}
|
||||
|
||||
val subId = repository.subIdInSimSlotFlow(SIM_SLOT_INDEX).firstWithTimeoutOrNull()
|
||||
|
||||
assertThat(subId).isEqualTo(SubscriptionManager.INVALID_SIM_SLOT_INDEX)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val SIM_SLOT_INDEX = 0
|
||||
const val SUB_ID = 1
|
||||
}
|
||||
}
|
||||
@@ -19,10 +19,14 @@ package com.android.settings.network.telephony.ims
|
||||
import android.content.Context
|
||||
import android.telephony.AccessNetworkConstants
|
||||
import android.telephony.ims.ImsMmTelManager
|
||||
import android.telephony.ims.ImsReasonInfo
|
||||
import android.telephony.ims.ImsRegistrationAttributes
|
||||
import android.telephony.ims.ImsStateCallback
|
||||
import android.telephony.ims.RegistrationManager.RegistrationCallback
|
||||
import android.telephony.ims.feature.MmTelFeature
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
|
||||
import com.android.settingslib.spa.testutils.toListWithTimeout
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import java.util.function.Consumer
|
||||
@@ -44,12 +48,17 @@ import org.mockito.kotlin.stub
|
||||
class ImsMmTelRepositoryTest {
|
||||
private val context: Context = ApplicationProvider.getApplicationContext()
|
||||
|
||||
private var registrationCallback: RegistrationCallback? = null
|
||||
private var stateCallback: ImsStateCallback? = null
|
||||
|
||||
private val mockImsMmTelManager = mock<ImsMmTelManager> {
|
||||
on { isVoWiFiSettingEnabled } doReturn true
|
||||
on { getVoWiFiRoamingModeSetting() } doReturn ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED
|
||||
on { getVoWiFiModeSetting() } doReturn ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED
|
||||
on { registerImsRegistrationCallback(any(), any<RegistrationCallback>()) } doAnswer {
|
||||
registrationCallback = it.arguments[1] as RegistrationCallback
|
||||
registrationCallback?.onRegistered(mock<ImsRegistrationAttributes>())
|
||||
}
|
||||
on { registerImsStateCallback(any(), any()) } doAnswer {
|
||||
stateCallback = it.arguments[1] as ImsStateCallback
|
||||
stateCallback?.onAvailable()
|
||||
@@ -99,6 +108,25 @@ class ImsMmTelRepositoryTest {
|
||||
assertThat(wiFiCallingMode).isEqualTo(ImsMmTelManager.WIFI_MODE_UNKNOWN)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun imsRegisteredFlow_sendInitialValue() = runBlocking {
|
||||
val imsRegistered = repository.imsRegisteredFlow().firstWithTimeoutOrNull()
|
||||
|
||||
assertThat(imsRegistered).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun imsRegisteredFlow_changed(): Unit = runBlocking {
|
||||
val listDeferred = async {
|
||||
repository.imsRegisteredFlow().toListWithTimeout()
|
||||
}
|
||||
delay(100)
|
||||
|
||||
registrationCallback?.onUnregistered(ImsReasonInfo())
|
||||
|
||||
assertThat(listDeferred.await().last()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun imsReadyFlow_sendInitialValue() = runBlocking {
|
||||
val flow = repository.imsReadyFlow()
|
||||
|
||||
@@ -29,6 +29,7 @@ import androidx.compose.ui.test.assertIsEnabled
|
||||
import androidx.compose.ui.test.assertIsNotEnabled
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.util.trace
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.settings.R
|
||||
@@ -36,10 +37,10 @@ import com.android.settingslib.spa.testutils.delay
|
||||
import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
|
||||
import com.android.settingslib.spaprivileged.model.app.userId
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.spy
|
||||
@@ -54,51 +55,30 @@ class AppForceStopButtonTest {
|
||||
|
||||
private val mockDevicePolicyManager = mock<DevicePolicyManager>()
|
||||
|
||||
private val mockUserManager = mock<UserManager> {
|
||||
on { getUserRestrictionSources(any(), any()) } doReturn emptyList()
|
||||
}
|
||||
|
||||
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
|
||||
on { packageManager } doReturn mockPackageManager
|
||||
on { devicePolicyManager } doReturn mockDevicePolicyManager
|
||||
on { getSystemService(Context.DEVICE_POLICY_SERVICE) } doReturn mockDevicePolicyManager
|
||||
on { getSystemService(Context.USER_SERVICE) } doReturn mockUserManager
|
||||
}
|
||||
|
||||
private val packageInfoPresenter = mock<PackageInfoPresenter> {
|
||||
on { context } doReturn context
|
||||
}
|
||||
|
||||
private val appForceStopButton = AppForceStopButton(packageInfoPresenter)
|
||||
|
||||
@Test
|
||||
fun getActionButton_isActiveAdmin_buttonDisabled() {
|
||||
val app = createApp()
|
||||
mockDevicePolicyManager.stub {
|
||||
on { packageHasActiveAdmins(PACKAGE_NAME, app.userId) } doReturn true
|
||||
}
|
||||
|
||||
setForceStopButton(app)
|
||||
|
||||
composeTestRule.onNodeWithText(context.getString(R.string.force_stop)).assertIsNotEnabled()
|
||||
private val mockAppForceStopRepository = mock<AppForceStopRepository> {
|
||||
on { canForceStopFlow() } doReturn flowOf(false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getActionButton_isUninstallInQueue_buttonDisabled() {
|
||||
val app = createApp()
|
||||
mockDevicePolicyManager.stub {
|
||||
on { isUninstallInQueue(PACKAGE_NAME) } doReturn true
|
||||
}
|
||||
|
||||
setForceStopButton(app)
|
||||
|
||||
composeTestRule.onNodeWithText(context.getString(R.string.force_stop)).assertIsNotEnabled()
|
||||
}
|
||||
private val appForceStopButton = AppForceStopButton(
|
||||
packageInfoPresenter = packageInfoPresenter,
|
||||
appForceStopRepository = mockAppForceStopRepository,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun getActionButton_isStopped_buttonDisabled() {
|
||||
val app = createApp {
|
||||
flags = ApplicationInfo.FLAG_STOPPED
|
||||
val app = createApp()
|
||||
mockAppForceStopRepository.stub {
|
||||
on { canForceStopFlow() } doReturn flowOf(false)
|
||||
}
|
||||
|
||||
setForceStopButton(app)
|
||||
@@ -109,6 +89,9 @@ class AppForceStopButtonTest {
|
||||
@Test
|
||||
fun getActionButton_regularApp_buttonEnabled() {
|
||||
val app = createApp()
|
||||
mockAppForceStopRepository.stub {
|
||||
on { canForceStopFlow() } doReturn flowOf(true)
|
||||
}
|
||||
|
||||
setForceStopButton(app)
|
||||
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.Manifest
|
||||
import android.app.Activity
|
||||
import android.app.admin.DevicePolicyManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.os.UserHandle
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
|
||||
import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.argThat
|
||||
import org.mockito.kotlin.doAnswer
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.eq
|
||||
import org.mockito.kotlin.isNull
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.spy
|
||||
import org.mockito.kotlin.stub
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class AppForceStopRepositoryTest {
|
||||
|
||||
private val mockDevicePolicyManager = mock<DevicePolicyManager>()
|
||||
|
||||
private var resultCode = Activity.RESULT_CANCELED
|
||||
|
||||
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
|
||||
on { devicePolicyManager } doReturn mockDevicePolicyManager
|
||||
onGeneric {
|
||||
sendOrderedBroadcastAsUser(
|
||||
argThat { action == Intent.ACTION_QUERY_PACKAGE_RESTART },
|
||||
eq(UserHandle.CURRENT),
|
||||
eq(Manifest.permission.HANDLE_QUERY_PACKAGE_RESTART),
|
||||
any(),
|
||||
isNull(),
|
||||
eq(Activity.RESULT_CANCELED),
|
||||
isNull(),
|
||||
isNull(),
|
||||
)
|
||||
} doAnswer {
|
||||
val broadcastReceiver = spy(it.arguments[3] as BroadcastReceiver) {
|
||||
on { resultCode } doReturn resultCode
|
||||
}
|
||||
broadcastReceiver.onReceive(mock, it.arguments[0] as Intent)
|
||||
}
|
||||
}
|
||||
|
||||
private val packageInfoPresenter = mock<PackageInfoPresenter> {
|
||||
on { context } doReturn context
|
||||
}
|
||||
|
||||
private val repository = AppForceStopRepository(packageInfoPresenter)
|
||||
|
||||
@Test
|
||||
fun getActionButton_isActiveAdmin_returnFalse() = runBlocking {
|
||||
val app = mockApp {}
|
||||
mockDevicePolicyManager.stub {
|
||||
on { packageHasActiveAdmins(PACKAGE_NAME, app.userId) } doReturn true
|
||||
}
|
||||
|
||||
val canForceStop = repository.canForceStopFlow().firstWithTimeoutOrNull()
|
||||
|
||||
assertThat(canForceStop).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getActionButton_isUninstallInQueue_returnFalse() = runBlocking {
|
||||
mockApp {}
|
||||
mockDevicePolicyManager.stub {
|
||||
on { isUninstallInQueue(PACKAGE_NAME) } doReturn true
|
||||
}
|
||||
|
||||
val canForceStop = repository.canForceStopFlow().firstWithTimeoutOrNull()
|
||||
|
||||
assertThat(canForceStop).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun canForceStopFlow_notStopped_returnTrue() = runBlocking {
|
||||
mockApp { flags = 0 }
|
||||
|
||||
val canForceStop = repository.canForceStopFlow().firstWithTimeoutOrNull()
|
||||
|
||||
assertThat(canForceStop).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun canForceStopFlow_isStoppedAndQueryReturnCancel_returnFalse() = runBlocking {
|
||||
mockApp {
|
||||
flags = ApplicationInfo.FLAG_STOPPED
|
||||
}
|
||||
resultCode = Activity.RESULT_CANCELED
|
||||
|
||||
val canForceStop = repository.canForceStopFlow().firstWithTimeoutOrNull()
|
||||
|
||||
assertThat(canForceStop).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun canForceStopFlow_isStoppedAndQueryReturnOk_returnTrue() = runBlocking {
|
||||
mockApp {
|
||||
flags = ApplicationInfo.FLAG_STOPPED
|
||||
}
|
||||
resultCode = Activity.RESULT_OK
|
||||
|
||||
val canForceStop = repository.canForceStopFlow().firstWithTimeoutOrNull()
|
||||
|
||||
assertThat(canForceStop).isTrue()
|
||||
}
|
||||
|
||||
private fun mockApp(builder: ApplicationInfo.() -> Unit = {}) = packageInfoPresenter.stub {
|
||||
on { flow } doReturn MutableStateFlow(PackageInfo().apply {
|
||||
applicationInfo = createApp(builder)
|
||||
})
|
||||
}
|
||||
|
||||
private fun createApp(builder: ApplicationInfo.() -> Unit = {}) =
|
||||
ApplicationInfo().apply {
|
||||
packageName = PACKAGE_NAME
|
||||
uid = UID
|
||||
enabled = true
|
||||
}.apply(builder)
|
||||
|
||||
private companion object {
|
||||
const val PACKAGE_NAME = "package.name"
|
||||
const val UID = 10000
|
||||
}
|
||||
}
|
||||
@@ -418,7 +418,6 @@ public class SimStatusDialogControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void initialize_showImsRegistration_shouldNotRemoveImsRegistrationStateSetting() {
|
||||
mPersistableBundle.putBoolean(
|
||||
CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, true);
|
||||
@@ -429,7 +428,6 @@ public class SimStatusDialogControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void initialize_doNotShowImsRegistration_shouldRemoveImsRegistrationStateSetting() {
|
||||
mPersistableBundle.putBoolean(
|
||||
CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false);
|
||||
|
||||
Reference in New Issue
Block a user