Snap for 11889377 from 5d062852c9 to 24Q3-release

Change-Id: Ia34425ceeff4bcf0603aa5c8b60f82bf40fa5b54
This commit is contained in:
Android Build Coastguard Worker
2024-05-25 03:21:51 +00:00
66 changed files with 1129 additions and 625 deletions

View File

@@ -14,87 +14,65 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<LinearLayout
<!-- This is used to grab style attributes and apply them
to this layout -->
<com.google.android.setupdesign.GlifLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/udfps_layout"
android:id="@+id/glif_layout"
style="?attr/fingerprint_layout_theme"
android:layout_width="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:layout_height="match_parent"
android:orientation="horizontal">
<!-- This is used to grab style attributes and apply them
to this layout -->
<com.google.android.setupdesign.GlifLayout
android:id="@+id/dummy_glif_layout"
style="?attr/fingerprint_layout_theme"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
/>
>
<LinearLayout
android:layout_width="300dp"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/sud_layout_icon"
style="@style/SudGlifIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="fitStart"
android:src="@drawable/ic_lock" />
<TextView
android:id="@+id/title"
style="@style/SudGlifHeaderTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="2"
/>
<TextView
android:id="@+id/description"
style="@style/SudDescription.Glif"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="3"
android:paddingLeft="10dp"
android:paddingRight="10dp"
/>
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/illustration_lottie"
android:layout_width="match_parent"
android:layout_height="0dp"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:scaleType="centerInside"
android:visibility="gone"
app:lottie_autoPlay="true"
app:lottie_loop="true"
app:lottie_speed=".85"
/>
</LinearLayout>
<FrameLayout
android:id="@+id/layout_container"
style="@style/SudContentFrame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal|bottom"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"
>
<include layout="@layout/fingerprint_v2_udfps_enroll_view" />
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center|bottom"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical">
<FrameLayout
android:id="@+id/layout_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal|bottom"
android:clipChildren="false"
android:clipToPadding="false"
>
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/illustration_lottie"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipChildren="false"
android:clipToPadding="false"
android:scaleType="centerInside"
android:visibility="gone"
app:lottie_autoPlay="true"
app:lottie_loop="true"
app:lottie_speed=".85"
/>
<include layout="@layout/fingerprint_v2_udfps_enroll_view" />
</FrameLayout>
</LinearLayout>
</LinearLayout>
</com.google.android.setupdesign.GlifLayout>
</LinearLayout>

View File

@@ -18,6 +18,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="172dp"
android:paddingTop="8dp"
android:paddingBottom="16dp"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
@@ -37,7 +38,6 @@
android:id="@+id/usage_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="12dp"
android:orientation="horizontal">
<TextView android:id="@+id/data_usage_view"

View File

@@ -19,7 +19,6 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/udfps_layout"
style="?attr/fingerprint_layout_theme"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
@@ -27,65 +26,57 @@
<!-- This is used to grab style attributes and apply them
to this layout -->
<com.google.android.setupdesign.GlifLayout
android:id="@+id/dummy_glif_layout"
android:id="@+id/glif_layout"
style="?attr/fingerprint_layout_theme"
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="0dp"
android:visibility="gone"
/>
android:layout_weight="5"
>
<ImageView
android:id="@+id/sud_layout_icon"
style="@style/SudGlifIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="fitStart"
android:src="@drawable/ic_lock" />
<LinearLayout
style="@style/SudContentFrame"
android:id="@+id/sud_content_frame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/title"
style="@style/SudGlifHeaderTitle"
android:layout_width="match_parent"
android:layout_height="80dp"
android:ellipsize="end"
android:lines="2"
/>
<TextView
android:id="@+id/description"
style="@style/SudDescription.Glif"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="3"
android:paddingLeft="10dp"
android:paddingRight="10dp"
/>
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/illustration_lottie"
android:layout_width="match_parent"
android:layout_height="200dp"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:scaleType="centerInside"
app:lottie_autoPlay="true"
app:lottie_loop="true"
app:lottie_speed=".85" />
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/illustration_lottie"
android:layout_width="match_parent"
android:layout_height="200dp"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:scaleType="centerInside"
app:lottie_autoPlay="true"
app:lottie_loop="true"
app:lottie_speed=".85" />
</LinearLayout>
</com.google.android.setupdesign.GlifLayout>
<FrameLayout
android:id="@+id/layout_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="0dp"
android:layout_gravity="center_horizontal|bottom"
android:layout_weight="4"
android:clipToPadding="false"
android:clipChildren="false"
>
<include layout="@layout/fingerprint_v2_udfps_enroll_view" />
<Button
android:id="@+id/skip"
style="@style/SudGlifButton.Secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|left"
android:layout_marginStart="20dp"
android:layout_marginBottom="20dp"
android:text="@string/security_settings_fingerprint_enroll_enrolling_skip" />
</FrameLayout>
</LinearLayout>

View File

@@ -21,6 +21,8 @@
android:id="@+id/udfps_animation_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical">
<ImageView

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1240,6 +1240,28 @@
<string name="private_space_fingerprint_unlock_title">Fingerprint Unlock for private space</string>
<!-- Title for the Face unlock for private space preference. [CHAR LIMIT=60] -->
<string name="private_space_face_unlock_title">Face Unlock for private space</string>
<!-- Title for the Face and Fingerprint preference for private space. [CHAR LIMIT=60] -->
<string name="private_space_biometric_unlock_title">Face &amp; Fingerprint Unlock for private space</string>
<!-- Introduction title shown in private space fingerprint enrollment [CHAR LIMIT=90] -->
<string name="private_space_fingerprint_enroll_introduction_title">Set up Fingerprint Unlock for private space</string>
<!-- Introduction detail message shown in private space fingerprint enrollment dialog [CHAR LIMIT=NONE]-->
<string name ="private_space_fingerprint_enroll_introduction_message">Use your fingerprint to unlock your private space or verify it\u2019s you, like when you sign in to apps or approve a purchase</string>
<!-- Introduction description message shown in private space fingerprint enrollment introduction screen. [CHAR LIMIT=NONE] -->
<string name="private_space_fingerprint_enroll_introduction_footer_message">Your private space can be unlocked when you don\u2019t intend to, like if someone holds up your phone to your finger.</string>
<!-- Message shown in fingerprint enrollment dialog once enrollment is completed (private space) [CHAR LIMIT=NONE] -->
<string name="private_space_fingerprint_enroll_finish_message">Use your fingerprint to unlock your private space or to approve purchases</string>
<!-- Introduction title shown in private space face enrollment [CHAR LIMIT=90] -->
<string name="private_space_face_enroll_introduction_title">Set up Face Unlock for private space</string>
<!-- Introduction detail message shown in private space face enrollment dialog [CHAR LIMIT=NONE]-->
<string name ="private_space_face_enroll_introduction_message">Use your face to unlock your private space or verify it\u2019s you, like when you sign in to apps or approve a purchase</string>
<!-- Message on the face enrollment introduction page for private space that provides information about what could cause the phone to unlock. [CHAR LIMIT=NONE] -->
<string name="private_space_face_enroll_introduction_info_looking">Looking at the phone can unlock private space even when you don\u2019t intend to. Your private space can also be unlocked by someone who looks a lot like you, like an identical sibling, or if someone holds the device up to your face.</string>
<!-- Message on the face enrollment introduction page for private space that provides information about the relative security of face for unlocking the phone. [CHAR LIMIT=NONE] -->
<string name="private_space_face_enroll_introduction_info_less_secure">Using your face to unlock your private space may be less secure than a strong pattern, PIN, or password</string>
<!-- Text shown on the details of a toggle which disables/enables face unlock for private space, depending if the user's eyes are open. [CHAR LIMIT=NONE] -->
<string name="private_space_face_settings_require_attention_details">To unlock private space, your eyes must be open. For best results, take off sunglasses.</string>
<!-- Text shown in face settings in private space explaining what your face can be used for. [CHAR LIMIT=NONE] -->
<string name="private_space_face_settings_footer">Use your face to unlock your private space.\n\nKeep in mind:\nYou can only have one face set up at a time. To add another face, delete the current one.\n\nLooking at the phone can unlock it when you don\u2019t intend to.\n\nYour private space can be unlocked by someone else if your device is held up to your face.\n\nYour private space can be unlocked by someone who looks a lot like you, like an identical sibling.</string>
<!-- Biometric category title - biometric options for unlocking the device. [CHAR LIMIT=50] -->
<string name="private_space_category_ways_to_unlock">Ways to unlock</string>
<!-- Summary for one lock when device screen lock is used as private profile lock. [CHAR LIMIT=40] -->
@@ -11406,7 +11428,7 @@
<string name="media_output_default_summary">This device</string>
<!-- Summary for media output when audio sharing. [CHAR LIMIT=NONE] -->
<string name="media_output_audio_sharing">Audio sharing</string>
<string name="media_output_audio_sharing">Sharing audio</string>
<!-- Summary for media output settings when device is in ongoing call state. -->
<string name="media_out_summary_ongoing_call_state">Unavailable during calls</string>
@@ -11896,6 +11918,10 @@
<string name="sim_onboarding_progressbar_turning_sim_on">Turning on <xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g>&#8230;</string>
<!-- Title of service provider name(SPN) at mobile network settings page. [CHAR LIMIT=30] -->
<string name="mobile_network_spn_title">Mobile network</string>
<!-- At the mobile network page, title for primary IMEI for multi-sim devices -->
<string name="imei_primary">IMEI (primary)</string>
<!-- At the mobile network page, title for primary MEID for multi-sim devices -->
<string name="meid_primary">MEID (primary)</string>
<!-- Title of phone number at mobile network settings page. [CHAR LIMIT=30] -->
<string name="mobile_network_phone_number_title">Phone number</string>
<!-- Title of SIM label and color editor dialog at mobile network settings page. [CHAR LIMIT=30] -->

View File

@@ -17,7 +17,7 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="@string/private_space_biometric_title"
android:title="@string/private_space_biometric_unlock_title"
settings:searchable="false">
<com.android.settingslib.widget.TopIntroPreference

View File

@@ -1400,6 +1400,16 @@ public final class Utils extends com.android.settingslib.Utils {
&& userManager.isQuietModeEnabled(userHandle);
}
/**
* Returns true if the userId is a private profile, false otherwise.
*/
public static boolean isPrivateProfile(int userId, @NonNull Context context) {
final UserManager userManager = context.getSystemService(UserManager.class);
UserInfo userInfo = userManager.getUserInfo(userId);
return Flags.allowPrivateProfile() && android.multiuser.Flags.enablePrivateSpaceFeatures()
&& userInfo.isPrivateProfile();
}
/**
* Enable new edge to edge feature.
*

View File

@@ -559,7 +559,8 @@ public class AppButtonsPreferenceController extends BasePreferenceController imp
if (android.app.Flags.appRestrictionsApi()) {
am.noteAppRestrictionEnabled(pkgName, mAppEntry.info.uid,
ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED, true,
ActivityManager.RESTRICTION_REASON_USER, "settings", 0L);
ActivityManager.RESTRICTION_REASON_USER,
"settings", ActivityManager.RESTRICTION_SOURCE_USER, 0L);
}
am.forceStopPackage(pkgName);
int userId = UserHandle.getUserId(mAppEntry.info.uid);

View File

@@ -93,7 +93,10 @@ public class CombinedBiometricStatusUtils {
public String getTitle() {
UserManager userManager = mContext.getSystemService(UserManager.class);
if (userManager != null && userManager.isProfile()) {
return mContext.getString(R.string.security_settings_work_biometric_preference_title);
return mContext.getString(
Utils.isPrivateProfile(mUserId, mContext)
? R.string.private_space_biometric_unlock_title
: R.string.security_settings_work_biometric_preference_title);
} else {
return mContext.getString(R.string.security_settings_biometric_preference_title);
}

View File

@@ -365,7 +365,9 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
@StringRes
protected int getInfoMessageLooking() {
return R.string.security_settings_face_enroll_introduction_info_looking;
return isPrivateProfile()
? R.string.private_space_face_enroll_introduction_info_looking
: R.string.security_settings_face_enroll_introduction_info_looking;
}
@StringRes
@@ -390,7 +392,10 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
@StringRes
protected int getLessSecureMessage() {
return R.string.security_settings_face_enroll_introduction_info_less_secure;
return isPrivateProfile()
? R.string.private_space_face_enroll_introduction_info_less_secure
: R.string.security_settings_face_enroll_introduction_info_less_secure;
}
@Override
@@ -411,6 +416,9 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
@Override
protected int getHeaderResDefault() {
if (isPrivateProfile()) {
return R.string.private_space_face_enroll_introduction_title;
}
return R.string.security_settings_face_enroll_introduction_title;
}
@@ -577,7 +585,10 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
@Override
protected void updateDescriptionText() {
if (mIsFaceStrong) {
if (isPrivateProfile()) {
setDescriptionText(getString(
R.string.private_space_face_enroll_introduction_message));
} else if (mIsFaceStrong) {
setDescriptionText(getString(
R.string.security_settings_face_enroll_introduction_message_class3));
}
@@ -608,4 +619,8 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
}
updateDescriptionText();
}
private boolean isPrivateProfile() {
return Utils.isPrivateProfile(mUserId, getApplicationContext());
}
}

View File

@@ -211,6 +211,8 @@ public class FaceSettings extends DashboardFragment {
((FaceSettingsPreferenceController) controller).setUserId(mUserId);
} else if (controller instanceof FaceSettingsEnrollButtonPreferenceController) {
((FaceSettingsEnrollButtonPreferenceController) controller).setUserId(mUserId);
} else if (controller instanceof FaceSettingsFooterPreferenceController) {
((FaceSettingsFooterPreferenceController) controller).setUserId(mUserId);
}
}
mRemoveController.setUserId(mUserId);
@@ -367,6 +369,7 @@ public class FaceSettings extends DashboardFragment {
controllers.add(new FaceSettingsRemoveButtonPreferenceController(context));
controllers.add(new FaceSettingsConfirmPreferenceController(context));
controllers.add(new FaceSettingsEnrollButtonPreferenceController(context));
controllers.add(new FaceSettingsFooterPreferenceController(context));
return controllers;
}

View File

@@ -24,10 +24,12 @@ import android.hardware.face.FaceManager.GetFeatureCallback;
import android.hardware.face.FaceManager.SetFeatureCallback;
import android.provider.Settings;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;
import com.android.settings.R;
import com.android.settings.Utils;
/**
@@ -98,6 +100,18 @@ public class FaceSettingsAttentionPreferenceController extends FaceSettingsPrefe
mPreference = screen.findPreference(KEY);
}
@Override
public void updateState(@Nullable Preference preference) {
if (preference == null) {
return;
}
super.updateState(preference);
if (Utils.isPrivateProfile(getUserId(), mContext)) {
preference.setSummary(mContext.getString(
R.string.private_space_face_settings_require_attention_details));
}
}
@Override
public boolean isChecked() {
if (!FaceSettings.isFaceHardwareDetected(mContext)) {

View File

@@ -30,6 +30,7 @@ import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.utils.AnnotationSpan;
@@ -41,12 +42,17 @@ import java.util.List;
* Footer for face settings showing the help text and help link.
*/
public class FaceSettingsFooterPreferenceController extends BasePreferenceController {
private static final String KEY = "security_face_footer";
private static final String TAG = "FaceSettingsFooterPreferenceController";
private static final String ANNOTATION_URL = "url";
private final FaceFeatureProvider mProvider;
private Preference mPreference;
private boolean mIsFaceStrong;
private int mUserId;
public FaceSettingsFooterPreferenceController(@NonNull Context context) {
this(context, KEY);
}
public FaceSettingsFooterPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mProvider = FeatureFactory.getFeatureFactory().getFaceFeatureProvider();
@@ -79,7 +85,9 @@ public class FaceSettingsFooterPreferenceController extends BasePreferenceContro
int footerRes;
boolean isAttentionSupported = mProvider.isAttentionSupported(mContext);
if (mIsFaceStrong) {
if (Utils.isPrivateProfile(mUserId, mContext)) {
footerRes = R.string.private_space_face_settings_footer;
} else if (mIsFaceStrong) {
footerRes = isAttentionSupported
? R.string.security_settings_face_settings_footer_class3
: R.string.security_settings_face_settings_footer_attention_not_supported;
@@ -92,6 +100,10 @@ public class FaceSettingsFooterPreferenceController extends BasePreferenceContro
mContext.getText(footerRes), linkInfo));
}
public void setUserId(int userId) {
mUserId = userId;
}
private void addAuthenticatorsRegisteredCallback(Context context) {
final FaceManager faceManager = context.getSystemService(FaceManager.class);
faceManager.addAuthenticatorsRegisteredCallback(

View File

@@ -67,7 +67,10 @@ public class FaceStatusUtils {
public String getTitle() {
UserManager userManager = mContext.getSystemService(UserManager.class);
if (userManager != null && userManager.isProfile()) {
return mContext.getString(R.string.security_settings_face_profile_preference_title);
return mContext.getString(
Utils.isPrivateProfile(mUserId, mContext)
? R.string.private_space_face_unlock_title
: R.string.security_settings_face_profile_preference_title);
} else {
return mContext.getString(R.string.security_settings_face_preference_title);
}

View File

@@ -78,7 +78,10 @@ public class FingerprintEnrollFinish extends BiometricEnrollBase {
setContentView(R.layout.fingerprint_enroll_finish);
}
setHeaderText(R.string.security_settings_fingerprint_enroll_finish_title);
setDescriptionText(R.string.security_settings_fingerprint_enroll_finish_v2_message);
setDescriptionText(Utils.isPrivateProfile(mUserId, getApplicationContext())
? R.string.private_space_fingerprint_enroll_finish_message
: R.string.security_settings_fingerprint_enroll_finish_v2_message);
final String sfpsDescription = mSfpsRestToUnlockFeature != null
? mSfpsRestToUnlockFeature.getDescriptionForSfps(this)
: null;

View File

@@ -176,7 +176,9 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
@Override
protected void initViews() {
setDescriptionText(getString(
R.string.security_settings_fingerprint_enroll_introduction_v3_message,
isPrivateProfile()
? R.string.private_space_fingerprint_enroll_introduction_message
: R.string.security_settings_fingerprint_enroll_introduction_v3_message,
DeviceHelper.getDeviceName(this)));
super.initViews();
@@ -261,6 +263,9 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
@StringRes
protected int getFooterMessage5() {
if (isPrivateProfile()) {
return R.string.private_space_fingerprint_enroll_introduction_footer_message;
}
return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_5;
}
@@ -292,6 +297,9 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
@Override
protected int getHeaderResDefault() {
if (isPrivateProfile()) {
return R.string.private_space_fingerprint_enroll_introduction_title;
}
return R.string.security_settings_fingerprint_enroll_introduction_title;
}
@@ -477,4 +485,8 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
data.putExtra(MultiBiometricEnrollHelper.EXTRA_SKIP_PENDING_ENROLL, true);
return data;
}
private boolean isPrivateProfile() {
return Utils.isPrivateProfile(mUserId, getApplicationContext());
}
}

View File

@@ -510,8 +510,9 @@ public class FingerprintSettings extends SubSettings {
mFooterColumns.add(column2);
} else {
final FooterColumn column = new FooterColumn();
column.mTitle = getString(
R.string.security_settings_fingerprint_enroll_introduction_v3_message,
column.mTitle = getString(isPrivateProfile()
? R.string.private_space_fingerprint_enroll_introduction_message
: R.string.security_settings_fingerprint_enroll_introduction_v3_message,
DeviceHelper.getDeviceName(getActivity()));
column.mLearnMoreClickListener = learnMoreClickListener;
column.mLearnMoreOverrideText = getText(
@@ -1130,6 +1131,10 @@ public class FingerprintSettings extends SubSettings {
}
};
private boolean isPrivateProfile() {
return Utils.isPrivateProfile(mUserId, getContext());
}
public static class DeleteFingerprintDialog extends InstrumentedDialogFragment
implements DialogInterface.OnClickListener {

View File

@@ -68,7 +68,10 @@ public class FingerprintStatusUtils {
public String getTitle() {
UserManager userManager = mContext.getSystemService(UserManager.class);
if (userManager != null && userManager.isProfile()) {
return mContext.getString(R.string.security_settings_work_fingerprint_preference_title);
return mContext.getString(
Utils.isPrivateProfile(mUserId, mContext)
? R.string.private_space_fingerprint_unlock_title
: R.string.security_settings_work_fingerprint_preference_title);
} else {
return mContext.getString(R.string.security_settings_fingerprint_preference_title);
}

View File

@@ -184,6 +184,7 @@ public class UdfpsEnrollHelper extends InstrumentedFragment {
*/
public void onAcquired(boolean isAcquiredGood) {
if (mListener != null) {
Log.e("JRM", "OnaCquired " + isAcquiredGood + " lastStepIsGood" + animateIfLastStep());
mListener.onAcquired(isAcquiredGood && animateIfLastStep());
}
}

View File

@@ -34,7 +34,7 @@ class DebuggingRepositoryImpl : DebuggingRepository {
*/
private val isBuildDebuggable = Build.IS_DEBUGGABLE
/** This flag indicates if udfps should use debug repos to supply data to its various views. */
private val udfpsEnrollmentDebugEnabled = true
private val udfpsEnrollmentDebugEnabled = false
override fun isDebuggingEnabled(): Boolean {
return isBuildDebuggable

View File

@@ -34,10 +34,10 @@ class EnrollStageInteractorImpl() : EnrollStageInteractor {
flowOf(
mapOf(
0.0f to EnrollStageModel.Center,
0.25f to EnrollStageModel.Guided,
0.5f to EnrollStageModel.Fingertip,
0.75f to EnrollStageModel.LeftEdge,
0.875f to EnrollStageModel.RightEdge,
0.065f to EnrollStageModel.Guided,
0.48f to EnrollStageModel.Fingertip,
0.584f to EnrollStageModel.LeftEdge,
0.792f to EnrollStageModel.RightEdge,
)
)
}

View File

@@ -33,6 +33,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.flow.update
/** This repository is responsible for collecting all state related to the enroll API. */
@@ -106,6 +107,30 @@ class FingerprintEnrollInteractorImpl(
streamEnded = true
enrollRequestOutstanding.update { false }
}
override fun onUdfpsPointerDown(sensorId: Int) {
trySend(FingerEnrollState.PointerDown(sensorId)).onFailure { error ->
Log.d(TAG, "onUdfpsPointerDown failed to send, due to $error")
}
}
override fun onUdfpsPointerUp(sensorId: Int) {
trySend(FingerEnrollState.PointerUp(sensorId)).onFailure { error ->
Log.d(TAG, "onUdfpsPointerUp failed to send, due to $error")
}
}
override fun onUdfpsOverlayShown() {
trySend(FingerEnrollState.OverlayShown).onFailure { error ->
Log.d(TAG, "OverlayShown failed to send, due to $error")
}
}
override fun onAcquired(isAcquiredGood: Boolean) {
trySend(FingerEnrollState.Acquired(isAcquiredGood)).onFailure { error ->
Log.d(TAG, "Acquired failed to send, due to $error")
}
}
}
val cancellationSignal = CancellationSignal()

View File

@@ -17,17 +17,12 @@
package com.android.settings.biometrics.fingerprint2.domain.interactor
import android.content.Context
import android.util.Log
import android.view.OrientationEventListener
import com.android.internal.R
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transform
/** Interactor which provides information about orientation */
@@ -35,7 +30,9 @@ interface OrientationInteractor {
/** A flow that contains the information about the orientation changing */
val orientation: Flow<Int>
/**
* A flow that contains the rotation info
* This indicates the surface rotation that hte view is currently in. For instance its possible to
* rotate a view to 90 degrees but for it to still be portrait mode. In this case, this flow
* should emit that we are in rotation 0 (SurfaceView.Rotation_0)
*/
val rotation: Flow<Int>
/**
@@ -50,8 +47,7 @@ interface OrientationInteractor {
fun getRotationFromDefault(rotation: Int): Int
}
class OrientationInteractorImpl(private val context: Context, activityScope: CoroutineScope) :
OrientationInteractor {
class OrientationInteractorImpl(private val context: Context) : OrientationInteractor {
override val orientation: Flow<Int> = callbackFlow {
val orientationEventListener =
@@ -62,9 +58,12 @@ class OrientationInteractorImpl(private val context: Context, activityScope: Cor
}
orientationEventListener.enable()
awaitClose { orientationEventListener.disable() }
}.shareIn(activityScope, SharingStarted.Eagerly, replay = 1)
}
override val rotation: Flow<Int> = orientation.transform { emit(context.display!!.rotation) }
override val rotation: Flow<Int> =
orientation.transform {
emit(context.display!!.rotation)
}
override val rotationFromDefault: Flow<Int> = rotation.map { getRotationFromDefault(it) }

View File

@@ -17,6 +17,7 @@
package com.android.settings.biometrics.fingerprint2.domain.interactor
import android.graphics.PointF
import android.util.Log
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
@@ -69,6 +70,7 @@ class UdfpsEnrollInteractorImpl(
override fun onEnrollmentStep(stepsRemaining: Int, totalStep: Int) {
val index = (totalStep - stepsRemaining) % guidedEnrollmentPoints.size
Log.e("JRM", "guided enroll step $index")
_guidedEnrollment.update { guidedEnrollmentPoints[index] }
}

View File

@@ -47,10 +47,10 @@ sealed class FingerEnrollState {
data class Acquired(val acquiredGood: Boolean) : FingerEnrollState()
/** Indicates a pointer down event has occurred */
data object PointerDown : FingerEnrollState()
data class PointerDown(val fingerId: Int) : FingerEnrollState()
/** Indicates a pointer up event has occurred */
data object PointerUp : FingerEnrollState()
data class PointerUp(val fingerId: Int) : FingerEnrollState()
/** Indicates the overlay has shown */
data object OverlayShown : FingerEnrollState()

View File

@@ -72,6 +72,7 @@ import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enroll
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.fragment.RFPSEnrollFragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.fragment.UdfpsEnrollFragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsLastStepViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintAction
@@ -126,6 +127,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
private lateinit var fingerprintFlowViewModel: FingerprintFlowViewModel
private lateinit var fingerprintEnrollConfirmationViewModel:
FingerprintEnrollConfirmationViewModel
private lateinit var udfpsLastStepViewModel: UdfpsLastStepViewModel
private lateinit var udfpsViewModel: UdfpsViewModel
private lateinit var enrollStageInteractor: EnrollStageInteractor
private val coroutineDispatcher = Dispatchers.Default
@@ -320,7 +322,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
foldStateInteractor = FoldStateInteractorImpl(context)
foldStateInteractor.onConfigurationChange(resources.configuration)
orientationInteractor = OrientationInteractorImpl(context, lifecycleScope)
orientationInteractor = OrientationInteractorImpl(context)
vibrationInteractor =
VibrationInteractorImpl(context.getSystemService(Vibrator::class.java)!!, context)
@@ -373,11 +375,15 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
fingerprintEnrollEnrollingViewModel,
navigationViewModel,
orientationInteractor,
fingerprintManagerInteractor,
),
)[RFPSViewModel::class.java]
enrollStageInteractor = EnrollStageInteractorImpl()
udfpsLastStepViewModel =
UdfpsLastStepViewModel(fingerprintEnrollEnrollingViewModel, vibrationInteractor)
udfpsViewModel =
ViewModelProvider(
this,
@@ -393,6 +399,9 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
backgroundViewModel,
fingerprintSensorRepo,
udfpsEnrollInteractor,
fingerprintManagerInteractor,
udfpsLastStepViewModel,
accessibilityInteractor,
),
)[UdfpsViewModel::class.java]
@@ -456,7 +465,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
step.exitTransition.toAnimation(),
)
.setReorderingAllowed(true)
.add(R.id.fragment_container_view, theClass::class.java, null)
.replace(R.id.fragment_container_view, theClass::class.java, null)
.commit()
navigationViewModel.update(
FingerprintAction.TRANSITION_FINISHED,

View File

@@ -20,15 +20,18 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintAction
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Enrollment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationViewModel
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
@@ -41,6 +44,7 @@ class RFPSViewModel(
private val fingerprintEnrollViewModel: FingerprintEnrollEnrollingViewModel,
private val navigationViewModel: FingerprintNavigationViewModel,
orientationInteractor: OrientationInteractor,
private val fingerprintManager: FingerprintManagerInteractor,
) : ViewModel() {
private val _textViewIsVisible = MutableStateFlow(false)
@@ -52,7 +56,16 @@ class RFPSViewModel(
/** Indicates if the icon should be animating or not */
val shouldAnimateIcon = _shouldAnimateIcon
private var enrollFlow: Flow<FingerEnrollState?> = fingerprintEnrollViewModel.enrollFlow
private var enrollFlow: Flow<FingerEnrollState?> =
fingerprintManager.sensorPropertiesInternal.filterNotNull().combine(
fingerprintEnrollViewModel.enrollFlow
) { props, enroll ->
if (props.sensorType == FingerprintSensorType.REAR) {
enroll
} else {
null
}
}
/**
* Enroll progress message with a replay of size 1 allowing for new subscribers to get the most
@@ -149,6 +162,7 @@ class RFPSViewModel(
private val fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel,
private val navigationViewModel: FingerprintNavigationViewModel,
private val orientationInteractor: OrientationInteractor,
private val fingerprintManager: FingerprintManagerInteractor,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
@@ -157,6 +171,7 @@ class RFPSViewModel(
fingerprintEnrollEnrollingViewModel,
navigationViewModel,
orientationInteractor,
fingerprintManager,
)
as T
}

View File

@@ -21,8 +21,10 @@ import android.util.Log
import android.view.MotionEvent
import android.view.MotionEvent.ACTION_HOVER_MOVE
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.TextView
import android.widget.Button
import android.widget.FrameLayout
import androidx.annotation.VisibleForTesting
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
@@ -40,7 +42,6 @@ import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enroll
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.EducationAnimationModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.widget.UdfpsEnrollViewV2
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep
import com.google.android.setupdesign.GlifLayout
import kotlinx.coroutines.launch
@@ -71,10 +72,8 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
val fragment = this
lottie = view.findViewById(R.id.illustration_lottie)!!
udfpsEnrollView = view.findViewById(R.id.udfps_animation_view)!!
val titleTextView = view.findViewById<TextView>(R.id.title)!!
val descriptionTextView = view.findViewById<TextView>(R.id.description)!!
val glifLayout: GlifLayout = view.findViewById(R.id.glif_layout)!!
val glifLayout = view.findViewById<GlifLayout>(R.id.dummy_glif_layout)!!
val backgroundColor = glifLayout.backgroundBaseColor
val window = requireActivity().window
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
@@ -83,6 +82,10 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
window.statusBarColor = color
view.setBackgroundColor(color)
view.findViewById<Button>(R.id.skip)?.apply {
setOnClickListener { viewModel.negativeButtonClicked() }
}
udfpsEnrollView.setFinishAnimationCompleted { viewModel.finishedSuccessfully() }
viewLifecycleOwner.lifecycleScope.launch {
@@ -92,32 +95,41 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
udfpsEnrollView.setSensorRect(sensor.sensorBounds, sensor.sensorType)
}
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.headerText.collect { titleTextView.setText(it.toResource()) }
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.descriptionText.collect {
if (it != null) {
it.toResource()?.let { text -> descriptionTextView.setText(text) }
} else {
descriptionTextView.text = ""
launch { viewModel.overlayShown.collect { udfpsEnrollView.overlayShown() } }
launch { viewModel.headerText.collect { glifLayout.setHeaderText(it.toResource()) } }
launch {
viewModel.userInteractedWithSensor.collect {
if (!it) {
glifLayout.setHeaderText(R.string.security_settings_fingerprint_enroll_udfps_title)
glifLayout.setDescriptionText(R.string.security_settings_udfps_enroll_start_message)
}
}
}
viewLifecycleOwner.lifecycleScope.launch {
launch {
viewModel.descriptionText.collect {
if (it != null) {
it.toResource()?.let { text -> glifLayout.setDescriptionText(text) }
} else {
glifLayout.descriptionText = ""
}
}
}
launch {
viewModel.shouldShowLottie.collect {
lottie.visibility = if (it) View.VISIBLE else View.GONE
}
}
viewLifecycleOwner.lifecycleScope.launch {
launch {
viewModel.lottie.collect { lottieModel ->
if (lottie.visibility == View.GONE) {
return@collect
}
val resource = lottieModel.toResource()
if (resource != null) {
glifLayout.descriptionTextView.visibility = View.GONE
LottieCompositionFactory.fromRawRes(requireContext(), resource).addListener { comp ->
comp?.let { composition ->
lottie.setComposition(composition)
@@ -126,27 +138,24 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
}
}
} else {
glifLayout.descriptionTextView.visibility = View.VISIBLE
lottie.visibility = View.INVISIBLE
}
}
}
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.DESTROYED) { viewModel.stopEnrollment() }
}
viewLifecycleOwner.lifecycleScope.launch {
launch {
viewModel.accessibilityEnabled.collect { enabled ->
udfpsEnrollView.setAccessibilityEnabled(enabled)
}
}
viewLifecycleOwner.lifecycleScope.launch {
launch {
viewModel.enrollState.collect {
Log.d(TAG, "EnrollEvent $it")
if (it is FingerEnrollState.EnrollError) {
try {
FingerprintErrorDialog.showInstance(it, fragment)
viewModel.errorDialogShown(it)
} catch (exception: Exception) {
Log.e(TAG, "Exception occurred $exception")
}
@@ -156,19 +165,40 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
}
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.progressSaved.collect { udfpsEnrollView.onEnrollProgressSaved(it) }
launch { viewModel.progressSaved.collect { udfpsEnrollView.onEnrollProgressSaved(it) } }
launch { viewModel.guidedEnrollment.collect { udfpsEnrollView.updateGuidedEnrollment(it) } }
launch {
viewModel.guidedEnrollmentSaved.collect { udfpsEnrollView.onGuidedPointSaved(it) }
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.guidedEnrollment.collect {
glifLayout.post { udfpsEnrollView.updateGuidedEnrollment(it) }
launch { viewModel.shouldDrawIcon.collect { udfpsEnrollView.shouldDrawIcon(it) } }
// Hack to get the final step of enroll progress to animate.
launch {
viewModel.udfpsLastStepViewModel.shouldAnimateCompletion.collect {
Log.d(TAG, "Sending fake last enroll event $it")
udfpsEnrollView.onUdfpsEvent(it)
}
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.guidedEnrollmentSaved.collect {
glifLayout.post { udfpsEnrollView.onGuidedPointSaved(it) }
}
}
}
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.DESTROYED) { viewModel.stopEnrollment() }
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.isLandscape.collect {
if (it) {
changeViewToLandscape()
}
}
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.isReverseLandscape.collect {
if (it) {
changeViewToReverseLandscape()
}
}
}
@@ -183,12 +213,70 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
viewModel.readyForEnrollment()
}
private fun changeViewToReverseLandscape() {
Log.d(TAG, "changeViewToReverseLandscape")
val glifContainer = requireView().findViewById<GlifLayout>(R.id.glif_layout)!!
val headerView =
glifContainer.findViewById<View>(
com.google.android.setupdesign.R.id.sud_landscape_header_area
)
// The landscape_header_area nad landscape_content_area should have the same parent
val parent = headerView!!.parent as ViewGroup
val sudContentFrame =
glifContainer.findViewById<View>(
com.google.android.setupdesign.R.id.sud_landscape_content_area
)!!
val udfpsContainer = requireView().findViewById<FrameLayout>(R.id.layout_container)!!
parent.removeView(headerView)
parent.removeView(sudContentFrame)
parent.addView(sudContentFrame)
parent.addView(headerView)
unclipSubviewsFromParent(udfpsContainer)
udfpsEnrollView.requestLayout()
}
private fun changeViewToLandscape() {
Log.d(TAG, "changeViewToLandscape")
val glifContainer = requireView().findViewById<GlifLayout>(R.id.glif_layout)!!
val headerView =
glifContainer.findViewById<View>(
com.google.android.setupdesign.R.id.sud_landscape_header_area
)
// The landscape_header_area nad landscape_content_area should have the same parent
val parent = headerView!!.parent as ViewGroup
val sudContentFrame =
glifContainer.findViewById<View>(
com.google.android.setupdesign.R.id.sud_landscape_content_area
)!!
parent.removeView(headerView)
parent.removeView(sudContentFrame)
parent.addView(headerView)
parent.addView(sudContentFrame)
val udfpsContainer = requireView().findViewById<FrameLayout>(R.id.layout_container)!!
unclipSubviewsFromParent(udfpsContainer)
udfpsEnrollView.requestLayout()
}
private fun unclipSubviewsFromParent(view: View) {
var currParent = view.parent
while (currParent is ViewGroup) {
currParent.clipChildren = false
currParent.clipToPadding = false
currParent = currParent.parent
}
}
private fun HeaderText.toResource(): Int {
return when (this.enrollStageModel) {
EnrollStageModel.Center,
EnrollStageModel.Guided,
EnrollStageModel.Fingertip,
EnrollStageModel.Unknown -> R.string.security_settings_udfps_enroll_fingertip_title
EnrollStageModel.Guided -> R.string.security_settings_fingerprint_enroll_repeat_title
EnrollStageModel.Fingertip -> R.string.security_settings_udfps_enroll_fingertip_title
EnrollStageModel.Unknown -> R.string.security_settings_fingerprint_enroll_udfps_title
EnrollStageModel.LeftEdge -> R.string.security_settings_udfps_enroll_left_edge_title
EnrollStageModel.RightEdge -> R.string.security_settings_udfps_enroll_right_edge_title
}
@@ -218,6 +306,5 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
companion object {
private const val TAG = "UDFPSEnrollFragment"
private val navStep = FingerprintNavigationStep.Enrollment::class
}
}

View File

@@ -0,0 +1,22 @@
/*
* 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.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model
import android.graphics.PointF
import com.android.systemui.biometrics.shared.model.FingerprintSensor
data class UdfpsSensorLocation(val sensorLocation: FingerprintSensor, val offset: PointF?)

View File

@@ -0,0 +1,102 @@
/*
* 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.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintVibrationEffects
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
/**
* This class is responsible for determining if we should animate the last step of an enrollment. It
* seems to be due to poor hardware implementation that the last step of a UDFPS enrollment takes
* much longer then normal (likely due to to saving the whole enrollment/or some kind of
* verification)
*
* Because of this, we will use the information of if a fingerprint was acquired(good) + enrollment
* has reached the last step
*/
class UdfpsLastStepViewModel(
private val fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel,
private val vibrationInteractor: VibrationInteractor,
) : ViewModel() {
private val lastStep: MutableStateFlow<FingerEnrollState.EnrollProgress?> = MutableStateFlow(null)
private val stepSize: MutableStateFlow<Int?> = MutableStateFlow(null)
init {
viewModelScope.launch {
val steps =
fingerprintEnrollEnrollingViewModel.enrollFlow
.filterIsInstance<FingerEnrollState.EnrollProgress>()
.distinctUntilChanged()
.take(2)
.toList()
stepSize.update { steps[0].remainingSteps - steps[1].remainingSteps }
lastStep.update { FingerEnrollState.EnrollProgress(0, steps[0].totalStepsRequired) }
}
}
private val enrollProgress =
fingerprintEnrollEnrollingViewModel.enrollFlow.filterIsInstance<
FingerEnrollState.EnrollProgress
>()
/** Indicates if we should animate the completion of udfps enrollment. */
val shouldAnimateCompletion =
enrollProgress
.transform { it ->
if (it.remainingSteps == stepSize.value) {
fingerprintEnrollEnrollingViewModel.enrollFlow
.filterIsInstance<FingerEnrollState.Acquired>()
.filter { acquired -> acquired.acquiredGood }
.collect {
vibrationInteractor.vibrate(
FingerprintVibrationEffects.UdfpsSuccess,
"UdfpsLastStepViewModel",
)
emit(lastStep.value)
}
}
}
.filterNotNull()
class UdfpsLastStepViewModelFactory(
private val fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel,
private val vibrationInteractor: VibrationInteractor,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return UdfpsLastStepViewModel(fingerprintEnrollEnrollingViewModel, vibrationInteractor) as T
}
}
}

View File

@@ -22,9 +22,10 @@ import android.view.Surface
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository
import com.android.settings.biometrics.fingerprint2.data.model.EnrollStageModel
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository
import com.android.settings.biometrics.fingerprint2.data.repository.SimulatedTouchEventsRepository
import com.android.settings.biometrics.fingerprint2.domain.interactor.AccessibilityInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.DebuggingInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractor
@@ -32,6 +33,7 @@ import com.android.settings.biometrics.fingerprint2.domain.interactor.Fingerprin
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.UdfpsEnrollInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model.DescriptionText
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model.HeaderText
@@ -41,6 +43,7 @@ import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Fing
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.combineTransform
@@ -51,6 +54,8 @@ import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
/** ViewModel used to drive UDFPS Enrollment through [UdfpsEnrollFragment] */
@@ -66,13 +71,25 @@ class UdfpsViewModel(
backgroundViewModel: BackgroundViewModel,
sensorRepository: FingerprintSensorRepository,
udfpsEnrollInteractor: UdfpsEnrollInteractor,
fingerprintManager: FingerprintManagerInteractor,
val udfpsLastStepViewModel: UdfpsLastStepViewModel,
accessibilityInteractor: AccessibilityInteractor,
) : ViewModel() {
private val isSetupWizard = flowOf(false)
private var shouldResetErollment = false
private var _enrollState: Flow<FingerEnrollState?> =
fingerprintEnrollEnrollingViewModel.enrollFlow
fingerprintManager.sensorPropertiesInternal.filterNotNull().combine(
fingerprintEnrollEnrollingViewModel.enrollFlow
) { props, enroll ->
if (props.sensorType.isUdfps()) {
enroll
} else {
null
}
}
/** The current state of the enrollment. */
var enrollState: Flow<FingerEnrollState> =
combine(fingerprintEnrollEnrollingViewModel.enrollFlowShouldBeRunning, _enrollState) {
@@ -86,48 +103,59 @@ class UdfpsViewModel(
}
.filterNotNull()
/** Indicates that overlay has been shown */
val overlayShown =
enrollState
.filterIsInstance<FingerEnrollState.OverlayShown>()
.shareIn(viewModelScope, SharingStarted.Eagerly, 1)
private var _userInteractedWithSensor = MutableStateFlow(false)
/**
* This indicates whether the user has interacted with the sensor or not. This indicates if we are
* in the initial state of the UI.
*/
val userInteractedWithSensor: Flow<Boolean> =
enrollState.transform {
val interactiveMessage =
when (it) {
is FingerEnrollState.Acquired,
is FingerEnrollState.EnrollError,
is FingerEnrollState.EnrollHelp,
is FingerEnrollState.EnrollProgress,
is FingerEnrollState.PointerDown,
is FingerEnrollState.PointerUp -> true
else -> false
}
val hasPreviouslyInteracted = _userInteractedWithSensor.value or interactiveMessage
_userInteractedWithSensor.update { hasPreviouslyInteracted }
emit(hasPreviouslyInteracted)
}
/**
* Forwards the property sensor information. This is typically used to recreate views that must be
* aligned with the sensor.
*/
val sensorLocation = sensorRepository.fingerprintSensor
/** Indicates if accessibility is enabled */
val accessibilityEnabled = flowOf(true).shareIn(viewModelScope, SharingStarted.Eagerly, 1)
init {
viewModelScope.launch {
enrollState
.combine(accessibilityEnabled) { event, isEnabled -> Pair(event, isEnabled) }
.collect {
if (
when (it.first) {
is FingerEnrollState.EnrollError -> true
is FingerEnrollState.EnrollHelp -> it.second
is FingerEnrollState.EnrollProgress -> true
else -> false
}
) {
vibrate(it.first)
}
}
}
viewModelScope.launch {
backgroundViewModel.background.filter { it }.collect { didGoToBackground() }
}
}
/**
* This indicates at which point the UI should offset the fingerprint sensor icon for guided
* enrollment.
*/
/** Indicates a step of guided enrollment, the ui should animate the icon to the new location. */
val guidedEnrollment: Flow<PointF> =
udfpsEnrollInteractor.guidedEnrollmentOffset.distinctUntilChanged()
udfpsEnrollInteractor.guidedEnrollmentOffset
.distinctUntilChanged()
.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 0)
/** The saved version of [guidedEnrollment] */
val guidedEnrollmentSaved: Flow<PointF> =
guidedEnrollment.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
private var _lastOrientation: Int? = null
/** In case of rotations we should ensure the UI does not re-animate the last state. */
private val shouldReplayLastEvent =
orientationInteractor.rotation.transform {
if (_lastOrientation != null && _lastOrientation!! != it) {
emit(true)
} else {
emit(false)
}
_lastOrientation = it
}
/**
* This is the saved progress, this is for when views are recreated and need saved state for the
@@ -136,8 +164,32 @@ class UdfpsViewModel(
var progressSaved: Flow<FingerEnrollState.EnrollProgress> =
enrollState
.filterIsInstance<FingerEnrollState.EnrollProgress>()
.filterNotNull()
.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
.combineTransform(shouldReplayLastEvent) { enroll, shouldReplay ->
if (shouldReplay) {
emit(enroll)
}
}
.shareIn(viewModelScope, SharingStarted.Eagerly, 1)
/** Indicates if accessibility is enabled */
val accessibilityEnabled =
accessibilityInteractor.isAccessibilityEnabled.shareIn(
this.viewModelScope,
SharingStarted.Eagerly,
replay = 1,
)
/** Indicates if we are in reverse landscape orientation. */
val isReverseLandscape =
orientationInteractor.rotation
.transform { emit(it == Surface.ROTATION_270) }
.distinctUntilChanged()
/** Indicates if we are in the landscape orientation */
val isLandscape =
orientationInteractor.rotation
.transform { emit(it == Surface.ROTATION_90) }
.distinctUntilChanged()
/** This sends touch exploration events only used for debugging purposes. */
val touchExplorationDebug: Flow<Point> =
@@ -170,6 +222,18 @@ class UdfpsViewModel(
.filterNotNull()
.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
/** The saved version of [guidedEnrollment] */
val guidedEnrollmentSaved: Flow<PointF> =
combineTransform(guidedEnrollment, shouldReplayLastEvent, enrollStage) {
point,
shouldReplay,
stage ->
if (shouldReplay && stage is EnrollStageModel.Guided) {
emit(point)
}
}
.shareIn(viewModelScope, SharingStarted.Eagerly, 1)
init {
viewModelScope.launch {
enrollState
@@ -200,7 +264,7 @@ class UdfpsViewModel(
}
viewModelScope.launch {
backgroundViewModel.background.filter { true }.collect { didGoToBackground() }
backgroundViewModel.background.filter { it }.collect { didGoToBackground() }
}
}
@@ -210,20 +274,12 @@ class UdfpsViewModel(
displayDensityInteractor.displayDensity,
displayDensityInteractor.defaultDisplayDensity,
displayDensityInteractor.fontScale,
orientationInteractor.rotation,
) { currDisplayDensity, defaultDisplayDensity, fontScale, rotation ->
val canShowLottieForRotation =
when (rotation) {
Surface.ROTATION_0 -> true
else -> false
}
canShowLottieForRotation &&
if (fontScale > 1.0f) {
false
} else {
defaultDisplayDensity == currDisplayDensity
}
) { currDisplayDensity, defaultDisplayDensity, fontScale ->
if (fontScale > 1.0f) {
false
} else {
defaultDisplayDensity == currDisplayDensity
}
}
.shareIn(viewModelScope, SharingStarted.Eagerly, 1)
@@ -234,6 +290,18 @@ class UdfpsViewModel(
}
.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
/** Indicates if we should or shold not draw the fingerprint icon */
val shouldDrawIcon: Flow<Boolean> =
enrollState.transform { state ->
when (state) {
is FingerEnrollState.EnrollProgress,
is FingerEnrollState.EnrollError,
is FingerEnrollState.PointerUp -> emit(true)
is FingerEnrollState.PointerDown -> emit(false)
else -> {}
}
}
private val shouldClearDescriptionText = enrollStage.map { it is EnrollStageModel.Unknown }
/** The description text for UDFPS enrollment */
@@ -267,12 +335,12 @@ class UdfpsViewModel(
/** Indicates the negative button has been clicked */
fun negativeButtonClicked() {
doReset()
navigationViewModel.update(
FingerprintAction.NEGATIVE_BUTTON_PRESSED,
navStep,
"$TAG#negativeButtonClicked",
)
doReset()
}
/** Indicates that an enrollment was completed */
@@ -282,7 +350,7 @@ class UdfpsViewModel(
}
/** Indicates that the application went to the background. */
private fun didGoToBackground() {
fun didGoToBackground() {
navigationViewModel.update(
FingerprintAction.DID_GO_TO_BACKGROUND,
navStep,
@@ -293,11 +361,7 @@ class UdfpsViewModel(
private fun doReset() {
_enrollState = fingerprintEnrollEnrollingViewModel.enrollFlow
progressSaved =
enrollState
.filterIsInstance<FingerEnrollState.EnrollProgress>()
.filterNotNull()
.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
_userInteractedWithSensor.update { false }
}
/** The lottie that should be shown for UDFPS Enrollment */
@@ -320,6 +384,15 @@ class UdfpsViewModel(
vibrationInteractor.vibrate(vibrationEvent, "UdfpsEnrollFragment")
}
/** Indicates an error sent by the HAL has been acknowledged by the user */
fun errorDialogShown(it: FingerEnrollState.EnrollError) {
navigationViewModel.update(
FingerprintAction.USER_CLICKED_FINISH,
navStep,
"${TAG}#userClickedStopEnrollingDialog",
)
}
class UdfpsEnrollmentFactory(
private val vibrationInteractor: VibrationInteractor,
private val displayDensityInteractor: DisplayDensityInteractor,
@@ -332,6 +405,9 @@ class UdfpsViewModel(
private val backgroundViewModel: BackgroundViewModel,
private val sensorRepository: FingerprintSensorRepository,
private val udfpsEnrollInteractor: UdfpsEnrollInteractor,
private val fingerprintManager: FingerprintManagerInteractor,
private val udfpsLastStepViewModel: UdfpsLastStepViewModel,
private val accessibilityInteractor: AccessibilityInteractor,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
@@ -348,6 +424,9 @@ class UdfpsViewModel(
backgroundViewModel,
sensorRepository,
udfpsEnrollInteractor,
fingerprintManager,
udfpsLastStepViewModel,
accessibilityInteractor,
)
as T
}

View File

@@ -36,8 +36,8 @@ import android.util.PathParser
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.core.animation.addListener
import androidx.core.graphics.toRect
import androidx.core.graphics.toRectF
import com.android.settings.R
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import kotlin.math.sin
/**
@@ -45,7 +45,6 @@ import kotlin.math.sin
* various stages of enrollment
*/
class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeSet?) : Drawable() {
private var targetAnimationDuration: Long = TARGET_ANIM_DURATION_LONG
private var targetAnimatorSet: AnimatorSet? = null
private val movingTargetFpIcon: Drawable
private val fingerprintDrawable: ShapeDrawable
@@ -55,7 +54,7 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
@ColorInt private var movingTargetFill = 0
private var currentScale = 1.0f
private var alpha = 0
private var guidedEnrollmentOffset: PointF? = null
private var stopDrawing = false
/**
* This is the physical location of the sensor. This rect will be updated by [drawSensorRectAt]
@@ -63,15 +62,12 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
private val sensorRectBounds: Rect = Rect()
/**
* The following values are used to describe where the icon should be drawn. [currX] and [currY]
* are changed based on the current guided enrollment step which is given by the
* [UdfpsEnrollHelperV2]
* The following values are used to describe where the icon should be drawn. [sensorLeftOffset]
* and [sensorTopOffset] are changed based on the current guided enrollment step which is given by
* the [UdfpsEnrollHelperV2]
*/
private var currX = 0f
private var currY = 0f
private var sensorWidth = 0f
private var sensorHeight = 0f
private var sensorLeftOffset = 0f
private var sensorTopOffset = 0f
init {
fingerprintDrawable = createUdfpsIcon(context)
@@ -131,29 +127,35 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
* The [sensorRect] coordinates for the sensor area. The [sensorRect] should be the coordinates
* with respect to its root frameview
*/
fun drawSensorRectAt(sensorRect: Rect) {
Log.e(TAG, "UdfpsEnrollIcon#drawSensorRect($sensorRect)")
fun drawSensorRectAt(overlayParams: UdfpsOverlayParams) {
Log.e(TAG, "UdfpsEnrollIcon#drawSensorRect(${overlayParams.sensorBounds})")
val sensorRect = overlayParams.sensorBounds
sensorRectBounds.set(sensorRect)
fingerprintDrawable.bounds = sensorRect
movingTargetFpIcon.bounds = sensorRect
currX = sensorRect.left.toFloat()
currY = sensorRect.top.toFloat()
sensorWidth = (sensorRect.right - sensorRect.left).toFloat()
sensorHeight = (sensorRect.bottom - sensorRect.top).toFloat()
// End existing animation if we get an update of the sensor rect.
targetAnimatorSet?.end()
invalidateSelf()
}
/** Stop drawing the fingerprint icon. */
fun stopDrawing() {
alpha = 0
stopDrawing = true
invalidateSelf()
}
/** Resume drawing the fingerprint icon */
fun startDrawing() {
alpha = 255
stopDrawing = false
invalidateSelf()
}
override fun draw(canvas: Canvas) {
if (stopDrawing) {
return
}
movingTargetFpIcon.alpha = alpha
fingerprintDrawable.setAlpha(alpha)
sensorOutlinePaint.setAlpha(alpha)
@@ -165,23 +167,28 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
fingerprintDrawable.draw(canvas)
}
private fun getCurrLocation(): RectF =
RectF(currX, currY, currX + sensorWidth, currY + sensorHeight)
private fun getCurrLocation(): RectF {
val x = sensorRectBounds.left + sensorLeftOffset
val y = sensorRectBounds.top + sensorTopOffset
return RectF(x, y, x + sensorRectBounds.width(), y + sensorRectBounds.height())
}
private fun animateMovement(currentBounds: Rect, offsetRect: RectF, scaleMovement: Boolean) {
if (currentBounds.equals(offsetRect)) {
private fun animateMovement(leftOffset: Float, topOffset: Float, scaleMovement: Boolean) {
if (leftOffset == sensorLeftOffset && topOffset == sensorTopOffset) {
return
}
val xAnimator = ValueAnimator.ofFloat(currentBounds.left.toFloat(), offsetRect.left)
val currLocation = getCurrLocation()
val xAnimator = ValueAnimator.ofFloat(currLocation.left - sensorRectBounds.left, leftOffset)
xAnimator.addUpdateListener {
currX = it.animatedValue as Float
sensorLeftOffset = it.animatedValue as Float
invalidateSelf()
}
val yAnimator = ValueAnimator.ofFloat(currentBounds.top.toFloat(), offsetRect.top)
val yAnimator = ValueAnimator.ofFloat(currLocation.top - sensorRectBounds.top, topOffset)
yAnimator.addUpdateListener {
currY = it.animatedValue as Float
sensorTopOffset = it.animatedValue as Float
invalidateSelf()
}
val animators = mutableListOf(xAnimator, yAnimator)
@@ -199,6 +206,7 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
animators.add(scaleAnimator)
}
targetAnimatorSet?.cancel()
targetAnimatorSet = AnimatorSet()
targetAnimatorSet?.let {
@@ -209,51 +217,14 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
}
}
/**
* This sets animation time to 0. This typically happens after an activity recreation, we don't
* want to re-animate the progress/success animation with the default timer
*/
private fun setAnimationTimeToZero() {
targetAnimationDuration = 0
}
/** This sets animation timers back to normal, this happens after we have */
private fun restoreAnimationTime() {
targetAnimationDuration = TARGET_ANIM_DURATION_LONG
}
/**
* Indicates a change to guided enrollment has occurred. Also indicates if we are recreating the
* view, in which case their is no need to animate the icon to whatever position it was in.
*/
fun updateGuidedEnrollment(point: PointF, isRecreating: Boolean) {
guidedEnrollmentOffset = point
if (isRecreating) {
setAnimationTimeToZero()
} else {
restoreAnimationTime()
}
val currentBounds = getCurrLocation().toRect()
val offset = guidedEnrollmentOffset
if (offset?.x != 0f && offset?.y != 0f) {
val targetRect = Rect(sensorRectBounds).toRectF()
// This is the desired location of the sensor rect, the [EnrollHelper]
// offsets the initial sensor rect by a bit to get the user to move their finger a bit more.
targetRect.offset(offset!!.x, offset!!.y)
val shouldAnimateMovement = !currentBounds.equals(targetRect)
if (shouldAnimateMovement) {
targetAnimatorSet?.cancel()
animateMovement(currentBounds, targetRect, true)
} else {
// If we are not offsetting the sensor, move it back to its original place
animateMovement(currentBounds, sensorRectBounds.toRectF(), false)
}
} else {
// If we are not offsetting the sensor, move it back to its original place
animateMovement(currentBounds, sensorRectBounds.toRectF(), false)
}
invalidateSelf()
fun updateGuidedEnrollment(point: PointF, isRecreating: Boolean) {
val pointIsZero = point.x == 0f && point.y == 0f
val shouldAnimateMovement = pointIsZero || !isRecreating
animateMovement(point?.x ?: 0f, point?.y ?: 0f, shouldAnimateMovement)
}
companion object {

View File

@@ -27,16 +27,15 @@ import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.util.DisplayMetrics
import android.util.Log
import android.view.animation.DecelerateInterpolator
import android.view.animation.Interpolator
import android.view.animation.OvershootInterpolator
import androidx.annotation.ColorInt
import androidx.core.animation.addListener
import androidx.core.animation.doOnEnd
import androidx.core.graphics.toRectF
import com.android.internal.annotations.VisibleForTesting
import com.android.settings.R
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import kotlin.math.cos
import kotlin.math.max
import kotlin.math.sin
@@ -145,7 +144,6 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
/** Indicates enrollment progress has occurred. */
fun onEnrollmentProgress(remaining: Int, totalSteps: Int, isRecreating: Boolean = false) {
afterFirstTouch = true
updateProgress(remaining, totalSteps, isRecreating)
}
@@ -216,8 +214,8 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
* Draws the progress with locations [sensorLocationX] [sensorLocationY], note these must be with
* respect to the parent framelayout.
*/
fun drawProgressAt(sensorRect: Rect) {
this.sensorRect.set(sensorRect)
fun drawProgressAt(overlayParams: UdfpsOverlayParams) {
this.sensorRect.set(overlayParams.sensorBounds)
invalidateSelf()
}
@@ -249,8 +247,6 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
restoreAnimationTime()
}
this.remainingSteps = remainingSteps
this.totalSteps = totalSteps
this.remainingSteps = remainingSteps
this.totalSteps = totalSteps
val targetProgress = (totalSteps - remainingSteps).toFloat().div(max(1, totalSteps))
@@ -290,12 +286,8 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
checkMarkDrawable.bounds = newBounds
checkMarkDrawable.setVisible(true, false)
}
doOnEnd {
onFinishedCompletionAnimation?.let{
it()
}
doOnEnd { onFinishedCompletionAnimation?.let { it() } }
}
start()
}
}
@@ -356,6 +348,7 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
private fun flashHelpFillColor() {
if (fillColorAnimator != null && fillColorAnimator!!.isRunning) {
fillColorAnimator!!.end()
fillColorAnimator = null
}
@ColorInt val targetColor = helpColor
fillColorAnimator =
@@ -375,7 +368,6 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
* want to re-animate the progress/success animation with the default timer
*/
private fun setAnimationTimeToZero() {
fillColorAnimationDuration = 0
animateArcDuration = 0
checkmarkAnimationDelayDuration = 0
checkmarkAnimationDuration = 0
@@ -383,7 +375,6 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
/** This sets animation timers back to normal, this happens after we have */
private fun restoreAnimationTime() {
fillColorAnimationDuration = FILL_COLOR_ANIMATION_DURATION_MS
animateArcDuration = PROGRESS_ANIMATION_DURATION_MS
checkmarkAnimationDelayDuration = CHECKMARK_ANIMATION_DELAY_MS
checkmarkAnimationDuration = CHECKMARK_ANIMATION_DURATION_MS

View File

@@ -70,27 +70,11 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
fun setSensorRect(rect: Rect, sensorType: FingerprintSensorType) {
this.sensorRect = rect
this.fingerprintSensorType = sensorType
findViewById<ImageView?>(R.id.udfps_enroll_animation_fp_progress_view)?.also {
it.setImageDrawable(fingerprintProgressDrawable)
}
findViewById<ImageView>(R.id.udfps_enroll_animation_fp_view)?.also {
it.setImageDrawable(fingerprintIcon)
}
val rotation = display.rotation
var displayInfo = DisplayInfo()
context.display.getDisplayInfo(displayInfo)
val rotation = displayInfo.rotation
val scaleFactor = udfpsUtils.getScaleFactor(displayInfo)
val overlayParams =
UdfpsOverlayParams(
sensorRect,
fingerprintProgressDrawable.bounds,
displayInfo.naturalWidth,
displayInfo.naturalHeight,
scaleFactor,
rotation,
sensorType.toInt(),
)
val parentView = parent as ViewGroup
val coords = parentView.getLocationOnScreen()
val parentLeft = coords[0]
@@ -99,22 +83,44 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
// If the view has been rotated, we need to translate the sensor coordinates
// to the new rotated view.
when (rotation) {
Surface.ROTATION_90,
Surface.ROTATION_270 -> {
Surface.ROTATION_90 -> {
sensorRectOffset.set(
sensorRectOffset.top,
sensorRectOffset.left,
sensorRectOffset.bottom,
sensorRectOffset.right,
)
sensorRectOffset.offset(-parentLeft, -parentTop)
}
else -> {}
}
// Translate the sensor position into UdfpsEnrollView's view space.
sensorRectOffset.offset(-parentLeft, -parentTop)
// When the view is rotated 270 degrees, 0,0 is the top corner left
Surface.ROTATION_270 -> {
sensorRectOffset.set(
(displayInfo.naturalHeight - sensorRectOffset.bottom) - parentLeft,
sensorRectOffset.left - parentTop,
(displayInfo.naturalHeight - sensorRectOffset.top) - parentLeft,
sensorRectOffset.right - parentTop,
)
}
else -> {
fingerprintIcon.drawSensorRectAt(sensorRectOffset)
fingerprintProgressDrawable.drawProgressAt(sensorRectOffset)
sensorRectOffset.offset(-parentLeft, -parentTop)
}
}
// Translate the sensor position into UdfpsEnrollView's view space.
val overlayParams =
UdfpsOverlayParams(
sensorRectOffset,
fingerprintProgressDrawable.bounds,
displayInfo.naturalWidth,
displayInfo.naturalHeight,
scaleFactor,
rotation,
sensorType.toInt(),
)
fingerprintIcon.drawSensorRectAt(overlayParams)
fingerprintProgressDrawable.drawProgressAt(overlayParams)
touchExplorationAnnouncer = TouchExplorationAnnouncer(context, this, overlayParams, udfpsUtils)
}
@@ -126,11 +132,8 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
onEnrollmentProgress(event.remainingSteps, event.totalStepsRequired)
is FingerEnrollState.Acquired -> onAcquired(event.acquiredGood)
is FingerEnrollState.EnrollHelp -> onEnrollmentHelp()
is FingerEnrollState.PointerDown -> onPointerDown()
is FingerEnrollState.PointerUp -> onPointerUp()
is FingerEnrollState.OverlayShown -> overlayShown()
is FingerEnrollState.EnrollError ->
throw IllegalArgumentException("$TAG should not handle udfps error")
// Else ignore
else -> {}
}
}
@@ -145,7 +148,6 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
}
}
private fun udfpsError(errMsgId: Int, errString: String) {}
/**
* Sends a touch exploration event to the [onHoverListener] this should only be used for
* debugging.
@@ -170,8 +172,15 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
onHoverListener = listener
}
private fun overlayShown() {
Log.e(TAG, "Implement overlayShown")
/** Indicates the overlay has been shown */
fun overlayShown() {
Log.d(TAG, "Showing udfps overlay")
findViewById<ImageView?>(R.id.udfps_enroll_animation_fp_progress_view)?.also {
it.setImageDrawable(fingerprintProgressDrawable)
}
findViewById<ImageView>(R.id.udfps_enroll_animation_fp_view)?.also {
it.setImageDrawable(fingerprintIcon)
}
}
/** Receive enroll progress event */
@@ -190,16 +199,6 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
if (animateIfLastStepGood) fingerprintProgressDrawable.onLastStepAcquired()
}
/** Receive onPointerDown event */
private fun onPointerDown() {
fingerprintIcon.stopDrawing()
}
/** Receive onPointerUp event */
private fun onPointerUp() {
fingerprintIcon.startDrawing()
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
// Because the layout has changed, we need to recompute all locations.
@@ -261,6 +260,17 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
fingerprintIcon.updateGuidedEnrollment(point, false)
}
/** Indicates if the enroll icon should be drawn. */
fun shouldDrawIcon(it: Boolean) {
post {
if (it) {
fingerprintIcon.startDrawing()
} else {
fingerprintIcon.stopDrawing()
}
}
}
companion object {
private const val TAG = "UdfpsEnrollView"
}

View File

@@ -16,6 +16,7 @@
package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.android.systemui.biometrics.shared.model.FingerprintSensor
@@ -68,7 +69,8 @@ class FingerprintEnrollEnrollingViewModel(
val enrollFlow =
enrollFlowShouldBeRunning.transformLatest {
if (it) {
fingerprintEnrollViewModel.enrollFlow.collect { event -> emit(event) }
fingerprintEnrollViewModel.enrollFlow.collect { event ->
emit(event) }
}
}
@@ -82,4 +84,8 @@ class FingerprintEnrollEnrollingViewModel(
as T
}
}
companion object {
private val TAG = "FingerprintEnrollEnrollingViewModel"
}
}

View File

@@ -66,6 +66,7 @@ import java.util.List;
public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment {
public static final String KEY_DEVICE_ADDRESS = "device_address";
private static final String TAG = "BTDeviceDetailsFrg";
private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
@VisibleForTesting
static int EDIT_DEVICE_NAME_ITEM_ID = Menu.FIRST;
@@ -95,11 +96,14 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
LocalBluetoothManager mManager;
@VisibleForTesting
CachedBluetoothDevice mCachedDevice;
BluetoothAdapter mBluetoothAdapter;
@Nullable
InputDevice mInputDevice;
private UserManager mUserManager;
int mExtraControlViewWidth = 0;
boolean mExtraControlUriLoaded = false;
private final BluetoothCallback mBluetoothCallback =
new BluetoothCallback() {
@@ -115,6 +119,16 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
}
};
private final BluetoothAdapter.OnMetadataChangedListener mExtraControlMetadataListener =
(device, key, value) -> {
if (key == METADATA_FAST_PAIR_CUSTOMIZED_FIELDS
&& mExtraControlViewWidth > 0
&& !mExtraControlUriLoaded) {
Log.i(TAG, "Update extra control UI because of metadata change.");
updateExtraControlUri(mExtraControlViewWidth);
}
};
public BluetoothDeviceDetailsFragment() {
super(DISALLOW_CONFIG_BLUETOOTH);
}
@@ -173,6 +187,7 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
public void onAttach(Context context) {
mDeviceAddress = getArguments().getString(KEY_DEVICE_ADDRESS);
mManager = getLocalBluetoothManager(context);
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mCachedDevice = getCachedDevice(mDeviceAddress);
mUserManager = getUserManager();
@@ -202,12 +217,18 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
: null);
mManager.getEventManager().registerCallback(mBluetoothCallback);
mBluetoothAdapter.addOnMetadataChangedListener(
mCachedDevice.getDevice(),
context.getMainExecutor(),
mExtraControlMetadataListener);
}
@Override
public void onDetach() {
super.onDetach();
mManager.getEventManager().unregisterCallback(mBluetoothCallback);
mBluetoothAdapter.removeOnMetadataChangedListener(
mCachedDevice.getDevice(), mExtraControlMetadataListener);
}
private void updateExtraControlUri(int viewWidth) {
@@ -222,9 +243,9 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
controlUri = Uri.parse(uri + viewWidth);
} catch (NullPointerException exception) {
Log.d(TAG, "unable to parse uri");
controlUri = null;
}
}
mExtraControlUriLoaded |= controlUri != null;
final SlicePreferenceController slicePreferenceController = use(
SlicePreferenceController.class);
slicePreferenceController.setSliceUri(sliceEnabled ? controlUri : null);
@@ -253,7 +274,8 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
if (view.getWidth() <= 0) {
return;
}
updateExtraControlUri(view.getWidth() - getPaddingSize());
mExtraControlViewWidth = view.getWidth() - getPaddingSize();
updateExtraControlUri(mExtraControlViewWidth);
view.getViewTreeObserver().removeOnGlobalLayoutListener(
mOnGlobalLayoutListener);
}

View File

@@ -20,6 +20,7 @@ import android.annotation.AttrRes;
import android.content.Context;
import android.graphics.Typeface;
import android.icu.text.MessageFormat;
import android.telephony.SubscriptionPlan;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
@@ -66,7 +67,7 @@ public class DataUsageSummaryPreference extends Preference {
@Nullable
private Long mCycleEndTimeMs;
/** The time of the last update in standard milliseconds since the epoch */
private long mSnapshotTimeMs;
private long mSnapshotTimeMs = SubscriptionPlan.TIME_UNKNOWN;
/** Name of carrier, or null if not available */
private CharSequence mCarrierName;
private CharSequence mLimitInfoText;

View File

@@ -393,7 +393,7 @@ public class BatteryUtils {
packageName, uid, ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED,
mode == AppOpsManager.MODE_IGNORED,
ActivityManager.RESTRICTION_REASON_USER,
"settings", 0);
"settings", ActivityManager.RESTRICTION_SOURCE_USER, 0L);
}
// Control whether app could run jobs in the background
mAppOpsManager.setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName, mode);

View File

@@ -767,6 +767,10 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
}
private class BatteryChartAccessibilityNodeProvider extends AccessibilityNodeProvider {
private static final int UNDEFINED = Integer.MIN_VALUE;
private int mAccessibilityFocusNodeViewId = UNDEFINED;
@Override
public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
if (virtualViewId == AccessibilityNodeProvider.HOST_VIEW_ID) {
@@ -794,6 +798,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
R.string.battery_usage_time_info_and_battery_level,
slotTimeInfo,
batteryLevelInfo));
childInfo.setAccessibilityFocused(virtualViewId == mAccessibilityFocusNodeViewId);
final Rect bounds = new Rect();
getBoundsOnScreen(bounds, true);
@@ -815,10 +820,14 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
return true;
case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
mAccessibilityFocusNodeViewId = virtualViewId;
return sendAccessibilityEvent(
virtualViewId, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
if (mAccessibilityFocusNodeViewId == virtualViewId) {
mAccessibilityFocusNodeViewId = UNDEFINED;
}
return sendAccessibilityEvent(
virtualViewId,
AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);

View File

@@ -329,38 +329,6 @@ public class UiccSlotUtil {
return INVALID_PHYSICAL_SLOT_ID;
}
// Device | |Slot |
// Working| |Mapping|
// State |Type |Mode |Friendly name
//--------------------------------------------------------------------------
// Single |SIM pSIM [RIL 0] |1 |pSIM active
// Single |SIM MEP Port #0 [RIL0] |2 |eSIM Port0 active
// Single |SIM MEP Port #1 [RIL0] |2.1 |eSIM Port1 active
// DSDS |pSIM [RIL 0] + MEP Port #0 [RIL 1] |3 |pSIM+Port0
// DSDS |pSIM [RIL 0] + MEP Port #1 [RIL 1] |3.1 |pSIM+Port1
// DSDS |MEP Port #0 [RIL 0] + MEP Port #1 [RIL1]|3.2 |Dual-Ports-A
// DSDS |MEP Port #1 [RIL 0] + MEP Port #0 [RIL1]|4 |Dual-Ports-B
//
// The rules are:
// 1. pSIM's logical slots always is [RIL 0].
// 2. assign the new active port to the same stack that will be de-activated
// For example: mode#3->mode#4
// 3. Add an eSIM carrier or enable eSIM carrier. The cases are at the below.
// 1) 1 => 2 / 2.1 / 3 / 3.1
// 2) 2 => 1 / 3 / 3.2
// 3) 2.1 => 3.1 / 4
// 4) 3 => 4
// 5) 3.1 => 3.2
// Note:
// 1) 2 <=> 2.1 blocked by LPA (reason: existing active port in SS so just re-use)
// 2) 3 <=> 3.1 blocked by LPA (reason: if pSIM+an active port, re-use the active port)
// 4. pSIM insertion or enabling
// 1) 2 => 1 / 3
// 2) 2.1 => 1 / 3.1
// 3) 3.2 => 3 / 3.1
// 4) 4 => 3 / 3.1
@VisibleForTesting
static Collection<UiccSlotMapping> prepareUiccSlotMappings(
Collection<UiccSlotMapping> uiccSlotMappings, boolean isPsim, int physicalSlotId,

View File

@@ -17,7 +17,6 @@
package com.android.settings.network.telephony
import android.content.Context
import android.os.UserManager
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
@@ -41,6 +40,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/**
* Preference controller for "IMEI"
*/
@@ -123,17 +123,21 @@ class MobileNetworkImeiPreferenceController(context: Context, key: String) :
ImeiInfoDialogFragment.show(fragment, simSlot, preference.title.toString())
return true
}
private fun getImei(): String {
val phoneType = getPhoneType()
return if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) mTelephonyManager.meid?: String()
else mTelephonyManager.imei?: String()
}
private fun getTitleForGsmPhone(): String {
return mContext.getString(R.string.status_imei)
return mContext.getString(
if (isPrimaryImei()) R.string.imei_primary else R.string.status_imei)
}
private fun getTitleForCdmaPhone(): String {
return mContext.getString(R.string.status_meid_number)
return mContext.getString(
if (isPrimaryImei()) R.string.meid_primary else R.string.status_meid_number)
}
private fun getTitle(): String {
@@ -142,6 +146,28 @@ class MobileNetworkImeiPreferenceController(context: Context, key: String) :
else getTitleForGsmPhone()
}
/**
* As per GSMA specification TS37, below Primary IMEI requirements are mandatory to support
*
* TS37_2.2_REQ_5
* TS37_2.2_REQ_8 (Attached the document has description about this test cases)
*/
protected fun isPrimaryImei(): Boolean {
val imei = getImei()
var primaryImei = String()
try {
primaryImei = mTelephonyManager.primaryImei
} catch (exception: Exception) {
Log.e(TAG, "PrimaryImei not available. $exception")
}
return primaryImei == imei && isMultiSim()
}
private fun isMultiSim(): Boolean {
return mTelephonyManager.activeModemCount > 1
}
fun getPhoneType(): Int {
return mTelephonyManager.currentPhoneType
}

View File

@@ -16,9 +16,10 @@
package com.android.settings.notification.modes;
import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
import android.service.notification.ZenPolicy;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -26,11 +27,17 @@ import androidx.preference.Preference;
import com.android.settingslib.core.AbstractPreferenceController;
import com.google.common.base.Preconditions;
import java.util.function.Function;
/**
* Base class for any preference controllers pertaining to any single Zen mode.
*/
abstract class AbstractZenModePreferenceController extends AbstractPreferenceController {
private static final String TAG = "AbstractZenModePreferenceController";
@Nullable
protected ZenModesBackend mBackend;
@@ -38,7 +45,7 @@ abstract class AbstractZenModePreferenceController extends AbstractPreferenceCon
private ZenMode mZenMode;
@NonNull
final String mKey;
private final String mKey;
// ZenModesBackend should only be passed in if the preference controller may set the user's
// policy for this zen mode. Otherwise, if the preference controller is essentially read-only
@@ -67,20 +74,56 @@ abstract class AbstractZenModePreferenceController extends AbstractPreferenceCon
updateState(preference);
}
@Nullable
public ZenMode getMode() {
return mZenMode;
@Override
public final void updateState(Preference preference) {
super.updateState(preference);
if (mZenMode != null) {
updateState(preference, mZenMode);
}
}
@Nullable
public AutomaticZenRule getAZR() {
if (mZenMode == null || mZenMode.getRule() == null) {
abstract void updateState(Preference preference, @NonNull ZenMode zenMode);
@Override
public final CharSequence getSummary() {
if (mZenMode != null) {
return getSummary(mZenMode);
} else {
return null;
}
return mZenMode.getRule();
}
/** Implementations of this class should override
* {@link AbstractPreferenceController#updateState(Preference)} to specify what should
* happen when the preference is updated */
@Nullable
protected CharSequence getSummary(@NonNull ZenMode zenMode) {
return null;
}
/**
* Subclasses should call this method (or a more specific one, like {@link #savePolicy} from
* their {@code onPreferenceChange()} or similar, in order to apply changes to the mode being
* edited (e.g. {@code saveMode(mode -> { mode.setX(value); return mode; } }.
*
* @param updater Function to update the {@link ZenMode}. Modifying and returning the same
* instance is ok.
*/
protected final boolean saveMode(Function<ZenMode, ZenMode> updater) {
Preconditions.checkState(mBackend != null);
ZenMode mode = mZenMode;
if (mode == null) {
Log.wtf(TAG, "Cannot save mode, it hasn't been loaded (" + getClass() + ")");
return false;
}
mode = updater.apply(mode);
mBackend.updateMode(mode);
return true;
}
protected final boolean savePolicy(Function<ZenPolicy.Builder, ZenPolicy.Builder> updater) {
return saveMode(mode -> {
ZenPolicy.Builder policyBuilder = new ZenPolicy.Builder(mode.getPolicy());
policyBuilder = updater.apply(policyBuilder);
mode.setPolicy(policyBuilder.build());
return mode;
});
}
}

View File

@@ -26,6 +26,7 @@ import android.app.AutomaticZenRule;
import android.app.NotificationManager;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenPolicy;
import android.util.Log;
@@ -150,14 +151,6 @@ class ZenMode {
}
}
/**
* Use sparingly. If you're updating a policy field, use
* {@link #setPolicy(android.service.notification.ZenPolicy)} instead.
*/
public void setAzr(@NonNull AutomaticZenRule newRule) {
mRule = newRule;
}
/**
* Updates the {@link ZenPolicy} of the associated {@link AutomaticZenRule} based on the
* supplied policy. In some cases this involves conversions, so that the following call
@@ -204,6 +197,13 @@ class ZenMode {
mRule.setZenPolicy(policy);
}
@NonNull
public ZenDeviceEffects getDeviceEffects() {
return mRule.getDeviceEffects() != null
? mRule.getDeviceEffects()
: new ZenDeviceEffects.Builder().build();
}
public boolean canBeDeleted() {
return !mIsManualDnd;
}

View File

@@ -20,12 +20,15 @@ import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_I
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
public class ZenModeCallsLinkPreferenceController extends AbstractZenModePreferenceController {
class ZenModeCallsLinkPreferenceController extends AbstractZenModePreferenceController {
private ZenModeSummaryHelper mSummaryHelper;
private final ZenModeSummaryHelper mSummaryHelper;
public ZenModeCallsLinkPreferenceController(Context context, String key,
ZenModesBackend backend) {
@@ -34,16 +37,15 @@ public class ZenModeCallsLinkPreferenceController extends AbstractZenModePrefere
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
bundle.putString(MODE_ID, getMode().getId());
bundle.putString(MODE_ID, zenMode.getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeCallsFragment.class.getName())
.setSourceMetricsCategory(0)
.setArguments(bundle)
.toIntent());
preference.setSummary(mSummaryHelper.getCallsSettingSummary(getMode()));
preference.setSummary(mSummaryHelper.getCallsSettingSummary(zenMode));
}
}

View File

@@ -16,16 +16,10 @@
package com.android.settings.notification.modes;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_ALARMS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_EVENTS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_MEDIA;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REMINDERS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_SYSTEM;
import android.app.AutomaticZenRule;
import android.content.Context;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenPolicy;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
@@ -38,9 +32,9 @@ public class ZenModeDisplayEffectPreferenceController extends AbstractZenModePre
}
@Override
public void updateState(Preference preference) {
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
TwoStatePreference pref = (TwoStatePreference) preference;
ZenDeviceEffects effects = getMode().getRule().getDeviceEffects();
ZenDeviceEffects effects = zenMode.getRule().getDeviceEffects();
if (effects == null) {
pref.setChecked(false);
} else {
@@ -62,33 +56,27 @@ public class ZenModeDisplayEffectPreferenceController extends AbstractZenModePre
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
final boolean allow = (Boolean) newValue;
ZenDeviceEffects currEffects = getMode().getRule().getDeviceEffects();
ZenDeviceEffects.Builder updatedEffects = currEffects == null
? new ZenDeviceEffects.Builder()
: new ZenDeviceEffects.Builder(getMode().getRule().getDeviceEffects());
switch (getPreferenceKey()) {
case "effect_greyscale":
updatedEffects.setShouldDisplayGrayscale(allow);
break;
case "effect_aod":
updatedEffects.setShouldSuppressAmbientDisplay(allow);
break;
case "effect_wallpaper":
updatedEffects.setShouldDimWallpaper(allow);
break;
case "effect_dark_theme":
updatedEffects.setShouldUseNightMode(allow);
break;
}
AutomaticZenRule updatedAzr = new AutomaticZenRule.Builder(getMode().getRule())
.setDeviceEffects(updatedEffects.build())
.build();
getMode().setAzr(updatedAzr);
mBackend.updateMode(getMode());
return true;
return saveMode(zenMode -> {
ZenDeviceEffects.Builder updatedEffects = new ZenDeviceEffects.Builder(
zenMode.getDeviceEffects());
switch (getPreferenceKey()) {
case "effect_greyscale":
updatedEffects.setShouldDisplayGrayscale(allow);
break;
case "effect_aod":
updatedEffects.setShouldSuppressAmbientDisplay(allow);
break;
case "effect_wallpaper":
updatedEffects.setShouldDimWallpaper(allow);
break;
case "effect_dark_theme":
updatedEffects.setShouldUseNightMode(allow);
break;
}
zenMode.getRule().setDeviceEffects(updatedEffects.build());
return zenMode;
});
}
}

View File

@@ -20,12 +20,15 @@ import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_I
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
public class ZenModeDisplayLinkPreferenceController extends AbstractZenModePreferenceController {
class ZenModeDisplayLinkPreferenceController extends AbstractZenModePreferenceController {
ZenModeSummaryHelper mSummaryHelper;
private final ZenModeSummaryHelper mSummaryHelper;
public ZenModeDisplayLinkPreferenceController(Context context, String key,
ZenModesBackend backend) {
@@ -34,10 +37,9 @@ public class ZenModeDisplayLinkPreferenceController extends AbstractZenModePrefe
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
bundle.putString(MODE_ID, getMode().getId());
bundle.putString(MODE_ID, zenMode.getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeDisplayFragment.class.getName())
@@ -47,7 +49,7 @@ public class ZenModeDisplayLinkPreferenceController extends AbstractZenModePrefe
}
@Override
public CharSequence getSummary() {
return mSummaryHelper.getDisplayEffectsSummary(getMode());
public CharSequence getSummary(@NonNull ZenMode zenMode) {
return mSummaryHelper.getDisplayEffectsSummary(zenMode);
}
}

View File

@@ -47,9 +47,8 @@ class ZenModeHeaderController extends AbstractZenModePreferenceController {
}
@Override
public void updateState(Preference preference) {
ZenMode mode = getMode();
if (mode == null || mFragment == null) {
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
if (mFragment == null) {
return;
}
@@ -62,9 +61,9 @@ class ZenModeHeaderController extends AbstractZenModePreferenceController {
}
FutureUtil.whenDone(
mode.getIcon(IconLoader.getInstance(mContext)),
zenMode.getIcon(IconLoader.getInstance(mContext)),
icon -> mHeaderController.setIcon(icon)
.setLabel(mode.getRule().getName())
.setLabel(zenMode.getRule().getName())
.done(false /* rebindActions */),
mContext.getMainExecutor());
}

View File

@@ -20,10 +20,13 @@ import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_I
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
public class ZenModeMessagesLinkPreferenceController extends AbstractZenModePreferenceController {
class ZenModeMessagesLinkPreferenceController extends AbstractZenModePreferenceController {
private final ZenModeSummaryHelper mSummaryHelper;
public ZenModeMessagesLinkPreferenceController(Context context, String key,
@@ -33,11 +36,9 @@ public class ZenModeMessagesLinkPreferenceController extends AbstractZenModePref
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
bundle.putString(MODE_ID, getMode().getId());
bundle.putString(MODE_ID, zenMode.getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeMessagesFragment.class.getName())
@@ -46,6 +47,6 @@ public class ZenModeMessagesLinkPreferenceController extends AbstractZenModePref
.toIntent());
preference.setEnabled(true);
preference.setSummary(mSummaryHelper.getMessagesSettingSummary(getMode().getPolicy()));
preference.setSummary(mSummaryHelper.getMessagesSettingSummary(zenMode.getPolicy()));
}
}

View File

@@ -20,12 +20,16 @@ import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_I
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
public class ZenModeNotifVisLinkPreferenceController extends AbstractZenModePreferenceController {
class ZenModeNotifVisLinkPreferenceController extends AbstractZenModePreferenceController {
private final ZenModeSummaryHelper mSummaryBuilder;
ZenModeSummaryHelper mSummaryBuilder;
public ZenModeNotifVisLinkPreferenceController(Context context, String key,
ZenModesBackend backend) {
super(context, key, backend);
@@ -33,10 +37,9 @@ public class ZenModeNotifVisLinkPreferenceController extends AbstractZenModePref
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
bundle.putString(MODE_ID, getMode().getId());
bundle.putString(MODE_ID, zenMode.getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeNotifVisFragment.class.getName())
@@ -46,7 +49,7 @@ public class ZenModeNotifVisLinkPreferenceController extends AbstractZenModePref
}
@Override
public CharSequence getSummary() {
return mSummaryBuilder.getBlockedEffectsSummary(getMode());
public CharSequence getSummary(@NonNull ZenMode zenMode) {
return mSummaryBuilder.getBlockedEffectsSummary(zenMode);
}
}

View File

@@ -18,9 +18,12 @@ package com.android.settings.notification.modes;
import android.content.Context;
import android.service.notification.ZenPolicy;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.preference.CheckBoxPreference;
import androidx.preference.Preference;
import com.android.settings.widget.DisabledCheckBoxPreference;
public class ZenModeNotifVisPreferenceController extends AbstractZenModePreferenceController
@@ -53,13 +56,13 @@ public class ZenModeNotifVisPreferenceController extends AbstractZenModePreferen
}
@Override
public void updateState(Preference preference) {
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
boolean suppressed = !getMode().getPolicy().isVisualEffectAllowed(mEffect, false);
boolean suppressed = !zenMode.getPolicy().isVisualEffectAllowed(mEffect, false);
boolean parentSuppressed = false;
if (mParentSuppressedEffects != null) {
for (@ZenPolicy.VisualEffect int parentEffect : mParentSuppressedEffects) {
if (!getMode().getPolicy().isVisualEffectAllowed(parentEffect, true)) {
if (!zenMode.getPolicy().isVisualEffectAllowed(parentEffect, true)) {
parentSuppressed = true;
}
}
@@ -77,15 +80,6 @@ public class ZenModeNotifVisPreferenceController extends AbstractZenModePreferen
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final boolean allowEffect = !((Boolean) newValue);
if (getMode().getPolicy().isVisualEffectAllowed(mEffect, true) != allowEffect) {
ZenPolicy diffPolicy = new ZenPolicy.Builder()
.showVisualEffect(mEffect, allowEffect)
.build();
getMode().setPolicy(diffPolicy);
mBackend.updateMode(getMode());
}
return true;
return savePolicy(policy -> policy.showVisualEffect(mEffect, allowEffect));
}
}

View File

@@ -16,20 +16,22 @@
package com.android.settings.notification.modes;
import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
/**
* Preference with a link and summary about what other sounds can break through the mode
*/
public class ZenModeOtherLinkPreferenceController extends AbstractZenModePreferenceController {
class ZenModeOtherLinkPreferenceController extends AbstractZenModePreferenceController {
ZenModeSummaryHelper mSummaryHelper;
private final ZenModeSummaryHelper mSummaryHelper;
public ZenModeOtherLinkPreferenceController(Context context, String key,
ZenModesBackend backend) {
@@ -38,15 +40,14 @@ public class ZenModeOtherLinkPreferenceController extends AbstractZenModePrefere
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
bundle.putString(MODE_ID, getMode().getId());
bundle.putString(MODE_ID, zenMode.getId());
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeOtherFragment.class.getName())
.setSourceMetricsCategory(0)
.setArguments(bundle)
.toIntent());
preference.setSummary(mSummaryHelper.getOtherSoundCategoriesSummary(getMode()));
preference.setSummary(mSummaryHelper.getOtherSoundCategoriesSummary(zenMode));
}
}

View File

@@ -23,11 +23,12 @@ import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REMINDERS
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_SYSTEM;
import android.content.Context;
import android.service.notification.ZenPolicy;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
public class ZenModeOtherPreferenceController extends AbstractZenModePreferenceController
class ZenModeOtherPreferenceController extends AbstractZenModePreferenceController
implements Preference.OnPreferenceChangeListener {
public ZenModeOtherPreferenceController(Context context, String key,
@@ -36,24 +37,15 @@ public class ZenModeOtherPreferenceController extends AbstractZenModePreferenceC
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
TwoStatePreference pref = (TwoStatePreference) preference;
pref.setChecked(getMode().getPolicy().isCategoryAllowed(getCategory(), true));
pref.setChecked(zenMode.getPolicy().isCategoryAllowed(getCategory(), true));
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final boolean allow = (Boolean) newValue;
ZenPolicy diffPolicy = new ZenPolicy.Builder()
.allowCategory(getCategory(), allow)
.build();
getMode().setPolicy(diffPolicy);
mBackend.updateMode(getMode());
return true;
return savePolicy(policy -> policy.allowCategory(getCategory(), allow));
}
private int getCategory() {

View File

@@ -16,21 +16,22 @@
package com.android.settings.notification.modes;
import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
/**
* Preference with a link and summary about what calls and messages can break through the mode
*/
public class ZenModePeopleLinkPreferenceController extends AbstractZenModePreferenceController {
class ZenModePeopleLinkPreferenceController extends AbstractZenModePreferenceController {
ZenModeSummaryHelper mSummaryHelper;
private final ZenModeSummaryHelper mSummaryHelper;
public ZenModePeopleLinkPreferenceController(Context context, String key,
ZenModesBackend backend) {
@@ -39,16 +40,15 @@ public class ZenModePeopleLinkPreferenceController extends AbstractZenModePrefer
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
bundle.putString(MODE_ID, getMode().getId());
bundle.putString(MODE_ID, zenMode.getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModePeopleFragment.class.getName())
.setSourceMetricsCategory(0)
.setArguments(bundle)
.toIntent());
preference.setSummary(mSummaryHelper.getPeopleSummary(getMode()));
preference.setSummary(mSummaryHelper.getPeopleSummary(zenMode));
}
}

View File

@@ -36,10 +36,13 @@ import android.provider.Contacts;
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.ZenPolicy;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.notification.app.ConversationListSettings;
@@ -59,7 +62,7 @@ import java.util.Map;
* bypass DND for calls or messages, which may be one of the following values: starred contacts, all
* contacts, priority conversations (for messages only), anyone, or no one.
*/
public class ZenModePrioritySendersPreferenceController
class ZenModePrioritySendersPreferenceController
extends AbstractZenModePreferenceController {
private final boolean mIsMessages; // if this is false, then this preference is for calls
@@ -124,12 +127,12 @@ public class ZenModePrioritySendersPreferenceController
}
@Override
public void updateState(Preference preference) {
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
if (mIsMessages) {
updateChannelCounts();
}
final int currContactsSetting = getPrioritySenders();
final int currConversationsSetting = getPriorityConversationSenders();
final int currContactsSetting = getPrioritySenders(zenMode.getPolicy());
final int currConversationsSetting = getPriorityConversationSenders(zenMode.getPolicy());
for (SelectorWithWidgetPreference pref : mSelectorPreferences) {
// for each preference, check whether the current state matches what this state
// would look like if the button were checked.
@@ -173,17 +176,17 @@ public class ZenModePrioritySendersPreferenceController
mNumImportantConversations = numImportantConversations;
}
private int getPrioritySenders() {
private int getPrioritySenders(ZenPolicy policy) {
if (mIsMessages) {
return getMode().getPolicy().getPriorityMessageSenders();
return policy.getPriorityMessageSenders();
} else {
return getMode().getPolicy().getPriorityCallSenders();
return policy.getPriorityCallSenders();
}
}
private int getPriorityConversationSenders() {
private int getPriorityConversationSenders(ZenPolicy policy) {
if (mIsMessages) {
return getMode().getPolicy().getPriorityConversationSenders();
return policy.getPriorityConversationSenders();
}
return CONVERSATION_SENDERS_UNSET;
}
@@ -419,29 +422,31 @@ public class ZenModePrioritySendersPreferenceController
@VisibleForTesting
SelectorWithWidgetPreference.OnClickListener mSelectorClickListener =
new SelectorWithWidgetPreference.OnClickListener() {
@Override
public void onRadioButtonClicked(SelectorWithWidgetPreference preference) {
// The settingsToSaveOnClick function takes whether the preference is a
// checkbox into account to determine whether this selection is checked or unchecked.
final int[] settingsToSave = settingsToSaveOnClick(preference,
getPrioritySenders(), getPriorityConversationSenders());
final int prioritySendersSetting = settingsToSave[0];
final int priorityConvosSetting = settingsToSave[1];
@Override
public void onRadioButtonClicked(SelectorWithWidgetPreference preference) {
savePolicy(policy -> {
ZenPolicy previousPolicy = policy.build();
// The settingsToSaveOnClick function takes whether the preference is a
// checkbox into account to determine whether this selection is checked or
// unchecked.
final int[] settingsToSave = settingsToSaveOnClick(preference,
getPrioritySenders(previousPolicy),
getPriorityConversationSenders(previousPolicy));
final int prioritySendersSetting = settingsToSave[0];
final int priorityConvosSetting = settingsToSave[1];
ZenPolicy.Builder diffPolicy = new ZenPolicy.Builder();
if (prioritySendersSetting != PEOPLE_TYPE_UNSET) {
if (mIsMessages) {
diffPolicy.allowMessages(prioritySendersSetting);
} else {
diffPolicy.allowCalls(prioritySendersSetting);
if (prioritySendersSetting != PEOPLE_TYPE_UNSET) {
if (mIsMessages) {
policy.allowMessages(prioritySendersSetting);
} else {
policy.allowCalls(prioritySendersSetting);
}
}
if (mIsMessages && priorityConvosSetting != CONVERSATION_SENDERS_UNSET) {
policy.allowConversations(priorityConvosSetting);
}
return policy;
});
}
}
if (mIsMessages && priorityConvosSetting != CONVERSATION_SENDERS_UNSET) {
diffPolicy.allowConversations(priorityConvosSetting);
}
getMode().setPolicy(diffPolicy.build());
mBackend.updateMode(getMode());
}
};
};
}

View File

@@ -17,20 +17,17 @@
package com.android.settings.notification.modes;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE;
import static android.service.notification.ZenPolicy.STATE_ALLOW;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.provider.Settings;
import android.service.notification.ZenPolicy;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;
import com.android.settings.R;
public class ZenModeRepeatCallersPreferenceController extends AbstractZenModePreferenceController
class ZenModeRepeatCallersPreferenceController extends AbstractZenModePreferenceController
implements Preference.OnPreferenceChangeListener {
private final int mRepeatCallersThreshold;
@@ -43,14 +40,12 @@ public class ZenModeRepeatCallersPreferenceController extends AbstractZenModePre
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
TwoStatePreference pref = (TwoStatePreference) preference;
boolean anyCallersCanBypassDnd =
getMode().getPolicy().getPriorityCategoryCalls() == STATE_ALLOW
&& getMode().getPolicy().getPriorityCallSenders() == PEOPLE_TYPE_ANYONE;
zenMode.getPolicy().getPriorityCategoryCalls() == STATE_ALLOW
&& zenMode.getPolicy().getPriorityCallSenders() == PEOPLE_TYPE_ANYONE;
// if any caller can bypass dnd then repeat callers preference is disabled
if (anyCallersCanBypassDnd) {
pref.setEnabled(false);
@@ -58,21 +53,16 @@ public class ZenModeRepeatCallersPreferenceController extends AbstractZenModePre
} else {
pref.setEnabled(true);
pref.setChecked(
getMode().getPolicy().getPriorityCategoryRepeatCallers() == STATE_ALLOW);
zenMode.getPolicy().getPriorityCategoryRepeatCallers() == STATE_ALLOW);
}
setRepeatCallerSummary(preference);
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
final boolean allowRepeatCallers = (Boolean) newValue;
ZenPolicy diffPolicy = new ZenPolicy.Builder()
.allowRepeatCallers(allowRepeatCallers)
.build();
getMode().setPolicy(diffPolicy);
mBackend.updateMode(getMode());
return true;
return savePolicy(policy -> policy.allowRepeatCallers(allowRepeatCallers));
}
private void setRepeatCallerSummary(Preference preference) {

View File

@@ -30,7 +30,6 @@ import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_MESSAGES;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REMINDERS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REPEAT_CALLERS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_SYSTEM;
import static android.service.notification.ZenPolicy.STATE_DISALLOW;
import static android.service.notification.ZenPolicy.VISUAL_EFFECT_AMBIENT;
import static android.service.notification.ZenPolicy.VISUAL_EFFECT_BADGE;
import static android.service.notification.ZenPolicy.VISUAL_EFFECT_FULL_SCREEN_INTENT;
@@ -44,7 +43,6 @@ import android.icu.text.MessageFormat;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenPolicy;
import android.util.SparseArray;
import com.android.settings.R;
import java.util.ArrayList;
@@ -54,10 +52,10 @@ import java.util.Locale;
import java.util.Map;
import java.util.function.Predicate;
public class ZenModeSummaryHelper {
class ZenModeSummaryHelper {
private Context mContext;
private ZenModesBackend mBackend;
private final Context mContext;
private final ZenModesBackend mBackend;
public ZenModeSummaryHelper(Context context, ZenModesBackend backend) {
mContext = context;

View File

@@ -39,7 +39,7 @@ import java.util.Map;
* containing links to each individual mode. This is a central controller that populates and updates
* all the preferences that then lead to a mode configuration page.
*/
public class ZenModesListPreferenceController extends BasePreferenceController {
class ZenModesListPreferenceController extends BasePreferenceController {
protected static final String KEY = "zen_modes_list";
@Nullable

View File

@@ -160,7 +160,8 @@ class PackageInfoPresenter(
context.activityManager.noteAppRestrictionEnabled(
packageName, uid,
ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED, true,
ActivityManager.RESTRICTION_REASON_USER, "settings", 0)
ActivityManager.RESTRICTION_REASON_USER, "settings",
ActivityManager.RESTRICTION_SOURCE_USER, 0)
}
context.activityManager.forceStopPackageAsUser(packageName, userId)
}

View File

@@ -43,6 +43,7 @@ import org.robolectric.shadows.ShadowAlarmManager;
import java.time.Clock;
import java.util.List;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
/** Tests of {@link BootBroadcastReceiver}. */
@@ -56,6 +57,7 @@ public final class BootBroadcastReceiverTest {
@Before
public void setUp() {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
mContext = ApplicationProvider.getApplicationContext();
mPeriodicJobManager = PeriodicJobManager.getInstance(mContext);
mShadowAlarmManager = shadowOf(mContext.getSystemService(AlarmManager.class));
@@ -136,7 +138,7 @@ public final class BootBroadcastReceiverTest {
mReceiver.onReceive(mContext, new Intent(Intent.ACTION_TIME_CHANGED));
TimeUnit.MILLISECONDS.sleep(100);
TimeUnit.MILLISECONDS.sleep(1000);
assertThat(mDao.getAllAfter(0)).isEmpty();
assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNotNull();
}
@@ -150,7 +152,7 @@ public final class BootBroadcastReceiverTest {
mReceiver.onReceive(mContext, new Intent(Intent.ACTION_TIME_CHANGED));
TimeUnit.MILLISECONDS.sleep(100);
TimeUnit.MILLISECONDS.sleep(1000);
assertThat(mDao.getAllAfter(0).size()).isEqualTo(1);
assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNotNull();
}
@@ -168,7 +170,7 @@ public final class BootBroadcastReceiverTest {
Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT,
Intent.EXTRA_TIME_PREF_VALUE_USE_12_HOUR));
TimeUnit.MILLISECONDS.sleep(100);
TimeUnit.MILLISECONDS.sleep(1000);
assertThat(mDao.getAllAfter(0).size()).isEqualTo(1);
assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNull();
}
@@ -182,7 +184,7 @@ public final class BootBroadcastReceiverTest {
mReceiver.onReceive(mContext, new Intent(Intent.ACTION_TIMEZONE_CHANGED));
TimeUnit.MILLISECONDS.sleep(100);
TimeUnit.MILLISECONDS.sleep(1000);
assertThat(mDao.getAllAfter(0)).isEmpty();
assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNotNull();
}

View File

@@ -19,18 +19,16 @@ package com.android.settings.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.service.notification.ZenPolicy.STATE_ALLOW;
import static android.service.notification.ZenPolicy.STATE_DISALLOW;
import static android.service.notification.ZenPolicy.STATE_UNSET;
import static android.service.notification.ZenPolicy.VISUAL_EFFECT_LIGHTS;
import static android.service.notification.ZenPolicy.VISUAL_EFFECT_NOTIFICATION_LIST;
import static android.service.notification.ZenPolicy.VISUAL_EFFECT_PEEK;
import static android.service.notification.ZenPolicy.VISUAL_EFFECT_STATUS_BAR;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -41,12 +39,10 @@ import android.content.res.Resources;
import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenPolicy;
import androidx.preference.TwoStatePreference;
import com.android.settings.notification.zen.ZenModeVisEffectPreferenceController;
import com.android.settings.widget.DisabledCheckBoxPreference;
import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -162,7 +158,7 @@ public final class ZenModeNotifVisPreferenceControllerTest {
assertThat(captor.getValue().getPolicy().getVisualEffectStatusBar())
.isEqualTo(STATE_DISALLOW);
assertThat(captor.getValue().getPolicy().getVisualEffectNotificationList())
.isEqualTo(STATE_UNSET);
.isEqualTo(STATE_DISALLOW); // Untouched
}
@Test
@@ -211,7 +207,7 @@ public final class ZenModeNotifVisPreferenceControllerTest {
assertThat(captor.getValue().getPolicy().getVisualEffectPeek())
.isEqualTo(STATE_ALLOW);
assertThat(captor.getValue().getPolicy().getVisualEffectNotificationList())
.isEqualTo(STATE_UNSET);
.isEqualTo(STATE_DISALLOW); // Untouched
}
@Test
@@ -236,6 +232,6 @@ public final class ZenModeNotifVisPreferenceControllerTest {
assertThat(captor.getValue().getPolicy().getVisualEffectPeek())
.isEqualTo(STATE_DISALLOW);
assertThat(captor.getValue().getPolicy().getVisualEffectNotificationList())
.isEqualTo(STATE_UNSET);
.isEqualTo(STATE_ALLOW); // Untouched
}
}

View File

@@ -50,6 +50,7 @@ public class ShadowUtils {
private static ArraySet<String> sResultLinks = new ArraySet<>();
private static boolean sIsBatteryPresent;
private static boolean sIsMultipleBiometricsSupported;
private static boolean sIsPrivateProfile;
@Implementation
protected static int enforceSameOwner(Context context, int userId) {
@@ -82,6 +83,7 @@ public class ShadowUtils {
sResultLinks = new ArraySet<>();
sIsBatteryPresent = true;
sIsMultipleBiometricsSupported = false;
sIsPrivateProfile = false;
}
public static void setIsDemoUser(boolean isDemoUser) {
@@ -188,4 +190,13 @@ public class ShadowUtils {
public static void setIsMultipleBiometricsSupported(boolean isMultipleBiometricsSupported) {
sIsMultipleBiometricsSupported = isMultipleBiometricsSupported;
}
@Implementation
protected static boolean isPrivateProfile(int userId, Context context) {
return sIsPrivateProfile;
}
public static void setIsPrivateProfile(boolean isPrivateProfile) {
sIsPrivateProfile = isPrivateProfile;
}
}

View File

@@ -118,7 +118,12 @@ class Injector(step: FingerprintNavigationStep.UiStep) {
var rfpsIconTouchViewModel = RFPSIconTouchViewModel()
var rfpsViewModel =
RFPSViewModel(fingerprintEnrollEnrollingViewModel, navigationViewModel, orientationInteractor)
RFPSViewModel(
fingerprintEnrollEnrollingViewModel,
navigationViewModel,
orientationInteractor,
interactor,
)
val fingerprintEnrollConfirmationViewModel =
FingerprintEnrollConfirmationViewModel(navigationViewModel, interactor)
@@ -151,7 +156,8 @@ class Injector(step: FingerprintNavigationStep.UiStep) {
BackgroundViewModel::class.java -> backgroundViewModel
RFPSIconTouchViewModel::class.java -> rfpsIconTouchViewModel
FingerprintEnrollEnrollingViewModel::class.java -> fingerprintEnrollEnrollingViewModel
FingerprintEnrollConfirmationViewModel::class.java -> fingerprintEnrollConfirmationViewModel
FingerprintEnrollConfirmationViewModel::class.java ->
fingerprintEnrollConfirmationViewModel
else -> null
}
as T

View File

@@ -19,18 +19,16 @@ package com.android.settings.network.telephony
import android.content.Context
import android.telephony.SubscriptionInfo
import android.telephony.TelephonyManager
import android.telephony.euicc.EuiccManager
import androidx.fragment.app.Fragment
import androidx.preference.Preference
import androidx.preference.PreferenceManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.internal.telephony.PhoneConstants
import com.android.settings.R
import com.android.settings.core.BasePreferenceController
import com.android.settings.network.SubscriptionInfoListViewModel
import com.android.settings.network.SubscriptionUtil
import com.android.settingslib.CustomDialogPreferenceCompat
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.runBlocking
import org.junit.After
@@ -62,6 +60,8 @@ class MobileNetworkImeiPreferenceControllerTest {
on { currentPhoneType } doReturn TelephonyManager.PHONE_TYPE_GSM
on { imei } doReturn mockImei
on { meid } doReturn mockImei
on { primaryImei } doReturn mockImei
on { activeModemCount } doReturn 2
}
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
@@ -90,7 +90,7 @@ class MobileNetworkImeiPreferenceControllerTest {
}
@Test
fun refreshData_getPhoneNumber_preferenceSummaryIsExpected() = runBlocking {
fun refreshData_getImei_preferenceSummaryIsExpected() = runBlocking {
whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(true)
whenever(SubscriptionUtil.getActiveSubscriptions(any())).thenReturn(
listOf(
@@ -110,6 +110,50 @@ class MobileNetworkImeiPreferenceControllerTest {
assertThat(preference.summary).isEqualTo(mockImei)
}
@Test
fun refreshData_getImeiTitle_showImei() = runBlocking {
whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(true)
whenever(SubscriptionUtil.getActiveSubscriptions(any())).thenReturn(
listOf(
SUB_INFO_1,
SUB_INFO_2
)
)
var mockSubId = 2
controller.init(mockFragment, mockSubId)
mockImei = "test imei"
mockTelephonyManager.stub {
on { imei } doReturn mockImei
on { primaryImei } doReturn ""
}
controller.refreshData(SUB_INFO_2)
assertThat(preference.title).isEqualTo(context.getString(R.string.status_imei))
}
@Test
fun refreshData_getPrimaryImeiTitle_showPrimaryImei() = runBlocking {
whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(true)
whenever(SubscriptionUtil.getActiveSubscriptions(any())).thenReturn(
listOf(
SUB_INFO_1,
SUB_INFO_2
)
)
var mockSubId = 2
controller.init(mockFragment, mockSubId)
mockImei = "test imei"
mockTelephonyManager.stub {
on { imei } doReturn mockImei
on { primaryImei } doReturn mockImei
}
controller.refreshData(SUB_INFO_2)
assertThat(preference.title).isEqualTo(context.getString(R.string.imei_primary))
}
@Test
fun getAvailabilityStatus_notSimHardwareVisible() {
whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(false)