From 4785c8a7ec9b53fab7a6b98196cce299eb7aa1b5 Mon Sep 17 00:00:00 2001 From: Bonian Chen Date: Fri, 6 Jan 2023 14:29:30 +0000 Subject: [PATCH 1/8] [Settings] Reduce CDMA API access when not required Avoid from accessing CDMA roaming API to reduce some latency on UI. Bug: 263294530 Bug: 264656126 Test: local Change-Id: I5c74636d8418a379724676e1f854c8adff5d4387 Merged-In: I5c74636d8418a379724676e1f854c8adff5d4387 --- .../CdmaSystemSelectPreferenceController.java | 46 +++++++++++++------ 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/src/com/android/settings/network/telephony/cdma/CdmaSystemSelectPreferenceController.java b/src/com/android/settings/network/telephony/cdma/CdmaSystemSelectPreferenceController.java index 5dd445379b8..4f4c35d62c9 100644 --- a/src/com/android/settings/network/telephony/cdma/CdmaSystemSelectPreferenceController.java +++ b/src/com/android/settings/network/telephony/cdma/CdmaSystemSelectPreferenceController.java @@ -18,10 +18,12 @@ package com.android.settings.network.telephony.cdma; import static com.android.settings.network.telephony.TelephonyConstants.TelephonyManagerConstants.NETWORK_MODE_LTE_GSM_WCDMA; import static com.android.settings.network.telephony.TelephonyConstants.TelephonyManagerConstants.NETWORK_MODE_NR_LTE_GSM_WCDMA; +import static com.android.settings.network.telephony.TelephonyConstants.TelephonyManagerConstants.NETWORK_MODE_UNKNOWN; import android.content.Context; import android.provider.Settings; import android.telephony.TelephonyManager; +import android.util.Log; import androidx.preference.ListPreference; import androidx.preference.Preference; @@ -33,6 +35,7 @@ import com.android.settings.network.telephony.MobileNetworkUtils; */ public class CdmaSystemSelectPreferenceController extends CdmaBasePreferenceController implements ListPreference.OnPreferenceChangeListener { + private static final String TAG = "CdmaSystemSelectPreferenceController"; public CdmaSystemSelectPreferenceController(Context context, String key) { super(context, key); @@ -42,23 +45,36 @@ public class CdmaSystemSelectPreferenceController extends CdmaBasePreferenceCont public void updateState(Preference preference) { super.updateState(preference); final ListPreference listPreference = (ListPreference) preference; - listPreference.setVisible(getAvailabilityStatus() == AVAILABLE); - final int mode = mTelephonyManager.getCdmaRoamingMode(); - if (mode != TelephonyManager.CDMA_ROAMING_MODE_RADIO_DEFAULT) { - if (mode == TelephonyManager.CDMA_ROAMING_MODE_HOME - || mode == TelephonyManager.CDMA_ROAMING_MODE_ANY) { - listPreference.setValue(Integer.toString(mode)); - } else { - resetCdmaRoamingModeToDefault(); - } + boolean isVisible = (getAvailabilityStatus() == AVAILABLE); + listPreference.setVisible(isVisible); + if (!isVisible) { + return; } + boolean hasTelephonyMgr = mTelephonyManager != null; + try { + final int mode = + hasTelephonyMgr ? mTelephonyManager.getCdmaRoamingMode() + : TelephonyManager.CDMA_ROAMING_MODE_RADIO_DEFAULT; + if (mode != TelephonyManager.CDMA_ROAMING_MODE_RADIO_DEFAULT) { + if (mode == TelephonyManager.CDMA_ROAMING_MODE_HOME + || mode == TelephonyManager.CDMA_ROAMING_MODE_ANY) { + listPreference.setValue(Integer.toString(mode)); + } else { + resetCdmaRoamingModeToDefault(); + } + } - final int settingsNetworkMode = MobileNetworkUtils.getNetworkTypeFromRaf( - (int) mTelephonyManager.getAllowedNetworkTypesForReason( - TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER)); - final boolean enableList = settingsNetworkMode != NETWORK_MODE_LTE_GSM_WCDMA - && settingsNetworkMode != NETWORK_MODE_NR_LTE_GSM_WCDMA; - listPreference.setEnabled(enableList); + final int settingsNetworkMode = + hasTelephonyMgr ? MobileNetworkUtils.getNetworkTypeFromRaf( + (int) mTelephonyManager.getAllowedNetworkTypesForReason( + TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER)) + : NETWORK_MODE_UNKNOWN; + final boolean enableList = settingsNetworkMode != NETWORK_MODE_LTE_GSM_WCDMA + && settingsNetworkMode != NETWORK_MODE_NR_LTE_GSM_WCDMA; + listPreference.setEnabled(enableList); + } catch (Exception exception) { + Log.e(TAG, "Fail to access framework API", exception); + } } @Override From 5342479f89d7c66305686847a2ed8343ff6c49b2 Mon Sep 17 00:00:00 2001 From: tom hsu Date: Mon, 16 Jan 2023 15:16:28 +0800 Subject: [PATCH 2/8] Pass viewable locales to LocalePickerWithRegion Bug: b/249219775 Test: Manual test Change-Id: I48f2f02a365d3597023e021a7819d31709e6d5d8 --- .../localepicker/AppLocalePickerActivity.java | 1 + .../localepicker/LocaleListEditor.java | 1 + .../LocalePickerWithRegionActivity.java | 23 +++++++++++++++++-- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/localepicker/AppLocalePickerActivity.java b/src/com/android/settings/localepicker/AppLocalePickerActivity.java index 2629b6ca639..aecc5710252 100644 --- a/src/com/android/settings/localepicker/AppLocalePickerActivity.java +++ b/src/com/android/settings/localepicker/AppLocalePickerActivity.java @@ -78,6 +78,7 @@ public class AppLocalePickerActivity extends SettingsBaseActivity mContextAsUser, this, false /* translate only */, + null, mPackageName, this); mAppLocaleDetails = AppLocaleDetails.newInstance(mPackageName, mContextAsUser.getUserId()); diff --git a/src/com/android/settings/localepicker/LocaleListEditor.java b/src/com/android/settings/localepicker/LocaleListEditor.java index 1d6fb47d76a..6ce72de2ce2 100644 --- a/src/com/android/settings/localepicker/LocaleListEditor.java +++ b/src/com/android/settings/localepicker/LocaleListEditor.java @@ -307,6 +307,7 @@ public class LocaleListEditor extends RestrictedSettingsFragment { final Intent intent = new Intent(getActivity(), LocalePickerWithRegionActivity.class); + intent.putExtras(getActivity().getIntent().getExtras()); startActivityForResult(intent, REQUEST_LOCALE_PICKER); } }); diff --git a/src/com/android/settings/localepicker/LocalePickerWithRegionActivity.java b/src/com/android/settings/localepicker/LocalePickerWithRegionActivity.java index a8c93c17944..9c7e6370db3 100644 --- a/src/com/android/settings/localepicker/LocalePickerWithRegionActivity.java +++ b/src/com/android/settings/localepicker/LocalePickerWithRegionActivity.java @@ -19,6 +19,9 @@ package com.android.settings.localepicker; import android.app.FragmentTransaction; import android.content.Intent; import android.os.Bundle; +import android.os.LocaleList; +import android.provider.Settings; +import android.util.Log; import android.view.MenuItem; import com.android.internal.app.LocalePickerWithRegion; @@ -29,7 +32,7 @@ import com.android.settings.core.SettingsBaseActivity; /** A activity to show the locale picker page. */ public class LocalePickerWithRegionActivity extends SettingsBaseActivity implements LocalePickerWithRegion.LocaleSelectedListener { - + private static final String TAG = LocalePickerWithRegionActivity.class.getSimpleName(); private static final String PARENT_FRAGMENT_NAME = "localeListEditor"; @Override @@ -37,9 +40,20 @@ public class LocalePickerWithRegionActivity extends SettingsBaseActivity super.onCreate(savedInstanceState); getActionBar().setDisplayHomeAsUpEnabled(true); setTitle(R.string.add_a_language); + LocaleList explicitLocales = null; + if (isDeviceDemoMode()) { + Bundle bundle = getIntent().getExtras(); + explicitLocales = bundle == null + ? null + : bundle.getParcelable(Settings.EXTRA_EXPLICIT_LOCALES, LocaleList.class); + Log.i(TAG, "Has explicit locales : " + explicitLocales); + } final LocalePickerWithRegion selector = LocalePickerWithRegion.createLanguagePicker( - this, LocalePickerWithRegionActivity.this, false /* translate only */); + this, + LocalePickerWithRegionActivity.this, + false /* translate only */, + explicitLocales); getFragmentManager() .beginTransaction() .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) @@ -78,5 +92,10 @@ public class LocalePickerWithRegionActivity extends SettingsBaseActivity finish(); } } + + private boolean isDeviceDemoMode() { + return Settings.Global.getInt( + getContentResolver(), Settings.Global.DEVICE_DEMO_MODE, 0) == 1; + } } From 73187f35992a5363b582253bf36146f557108769 Mon Sep 17 00:00:00 2001 From: Paul Hobbs Date: Wed, 18 Jan 2023 20:05:37 +0000 Subject: [PATCH 3/8] Revert "[RRS] show resolution option on Settings page." This reverts commit 0d9e5d600c3577bfae1af4009ac04ccbad38946b. Reason for revert: bad CL causing breakage in com.android.settings.display.ScreenResolutionFragmentTest https://screenshot.googleplex.com/AG6d44f3LTksHGx Change-Id: I522c6b5220c138b56af777627619ca986e2cd43c --- res/values/config.xml | 8 +- res/values/strings.xml | 10 ++- .../display/ScreenResolutionController.java | 90 ++++--------------- .../display/ScreenResolutionFragment.java | 56 ++++++------ 4 files changed, 58 insertions(+), 106 deletions(-) diff --git a/res/values/config.xml b/res/values/config.xml index 4d0c8914290..bbacc5c23ca 100755 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -562,7 +562,13 @@ @string/screen_resolution_option_high - @string/screen_resolution_option_full + @string/screen_resolution_option_highest + + + + + @string/screen_resolution_summary_high + @string/screen_resolution_summary_highest diff --git a/res/values/strings.xml b/res/values/strings.xml index fe0cd87fbdd..927f84d046f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -2102,10 +2102,14 @@ Screen resolution - + High resolution - - Full resolution + + Full resolution + + 1080p FHD+ + + 1440p QHD+ Full resolution uses more of your battery. Switching your resolution may cause some apps to restart. diff --git a/src/com/android/settings/display/ScreenResolutionController.java b/src/com/android/settings/display/ScreenResolutionController.java index a3433d405a5..dca12757e18 100644 --- a/src/com/android/settings/display/ScreenResolutionController.java +++ b/src/com/android/settings/display/ScreenResolutionController.java @@ -17,9 +17,7 @@ package com.android.settings.display; import android.content.Context; -import android.graphics.Point; import android.hardware.display.DisplayManager; -import android.util.Log; import android.view.Display; import androidx.annotation.VisibleForTesting; @@ -27,63 +25,32 @@ import androidx.annotation.VisibleForTesting; import com.android.settings.R; import com.android.settings.core.BasePreferenceController; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - /** Controller that switch the screen resolution. */ public class ScreenResolutionController extends BasePreferenceController { - private static final String TAG = "ScreenResolutionController"; - static final int HIGHRESOLUTION_IDX = 0; - static final int FULLRESOLUTION_IDX = 1; + + static final int FHD_WIDTH = 1080; + static final int QHD_WIDTH = 1440; private Display mDisplay; - private Set mSupportedResolutions = null; - private int mHighWidth = 0; - private int mFullWidth = 0; - private int mHighHeight = 0; - private int mFullHeight = 0; public ScreenResolutionController(Context context, String key) { super(context, key); mDisplay = mContext.getSystemService(DisplayManager.class).getDisplay(Display.DEFAULT_DISPLAY); - - initSupportedResolutionData(); } - /** - * Initialize the resolution data. So far, we support two resolution switching. Save the width - * and the height for high resolution and full resolution. - */ - private void initSupportedResolutionData() { - // Collect and filter the resolutions - Set resolutions = new HashSet<>(); + /** Check if the width is supported by the display. */ + private boolean isSupportedMode(int width) { for (Display.Mode mode : getSupportedModes()) { - resolutions.add(new Point(mode.getPhysicalWidth(), mode.getPhysicalHeight())); + if (mode.getPhysicalWidth() == width) return true; } - mSupportedResolutions = resolutions; - - // Get the width and height for high resolution and full resolution - List resolutionList = new ArrayList<>(resolutions); - if (resolutionList == null || resolutionList.size() != 2) { - Log.e(TAG, "No support"); - return; - } - - Collections.sort(resolutionList, (p1, p2) -> p1.x * p1.y - p2.x * p2.y); - mHighWidth = resolutionList.get(HIGHRESOLUTION_IDX).x; - mHighHeight = resolutionList.get(HIGHRESOLUTION_IDX).y; - mFullWidth = resolutionList.get(FULLRESOLUTION_IDX).x; - mFullHeight = resolutionList.get(FULLRESOLUTION_IDX).y; + return false; } /** Return true if the device contains two (or more) resolutions. */ protected boolean checkSupportedResolutions() { - return getHighWidth() != 0 && getFullWidth() != 0; + return isSupportedMode(FHD_WIDTH) && isSupportedMode(QHD_WIDTH); } @Override @@ -94,43 +61,20 @@ public class ScreenResolutionController extends BasePreferenceController { @Override public CharSequence getSummary() { String summary = null; - int width = getDisplayWidth(); - if (width == mHighWidth) { - summary = mContext.getString(R.string.screen_resolution_option_high); - } else if (width == mFullWidth) { - summary = mContext.getString(R.string.screen_resolution_option_full); - } else { - summary = mContext.getString(R.string.screen_resolution_title); + switch (getDisplayWidth()) { + case FHD_WIDTH: + summary = mContext.getString(R.string.screen_resolution_summary_high); + break; + case QHD_WIDTH: + summary = mContext.getString(R.string.screen_resolution_summary_highest); + break; + default: + summary = mContext.getString(R.string.screen_resolution_title); } return summary; } - /** Return all supported resolutions of the device. */ - public Set getAllSupportedResolutions() { - return this.mSupportedResolutions; - } - - /** Return the high resolution width of the device. */ - public int getHighWidth() { - return this.mHighWidth; - } - - /** Return the full resolution width of the device. */ - public int getFullWidth() { - return this.mFullWidth; - } - - /** Return the high resolution height of the device. */ - public int getHighHeight() { - return this.mHighHeight; - } - - /** Return the full resolution height of the device. */ - public int getFullHeight() { - return this.mFullHeight; - } - @VisibleForTesting public int getDisplayWidth() { return mDisplay.getMode().getPhysicalWidth(); diff --git a/src/com/android/settings/display/ScreenResolutionFragment.java b/src/com/android/settings/display/ScreenResolutionFragment.java index 687fdb88fea..665f6b08d69 100644 --- a/src/com/android/settings/display/ScreenResolutionFragment.java +++ b/src/com/android/settings/display/ScreenResolutionFragment.java @@ -16,6 +16,9 @@ package com.android.settings.display; +import static com.android.settings.display.ScreenResolutionController.FHD_WIDTH; +import static com.android.settings.display.ScreenResolutionController.QHD_WIDTH; + import android.annotation.Nullable; import android.app.settings.SettingsEnums; import android.content.Context; @@ -45,6 +48,7 @@ import com.android.settingslib.widget.IllustrationPreference; import com.android.settingslib.widget.SelectorWithWidgetPreference; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; @@ -55,6 +59,8 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment { private static final String TAG = "ScreenResolution"; private Resources mResources; + private static final int FHD_INDEX = 0; + private static final int QHD_INDEX = 1; private static final String SCREEN_RESOLUTION = "user_selected_resolution"; private Display mDefaultDisplay; private String[] mScreenResolutionOptions; @@ -65,9 +71,6 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment { private DisplayObserver mDisplayObserver; private AccessibilityManager mAccessibilityManager; - private int mHighWidth; - private int mFullWidth; - @Override public void onAttach(Context context) { super.onAttach(context); @@ -78,18 +81,11 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment { mResources = context.getResources(); mScreenResolutionOptions = mResources.getStringArray(R.array.config_screen_resolution_options_strings); + mScreenResolutionSummaries = + mResources.getStringArray(R.array.config_screen_resolution_summaries_strings); + mResolutions = getAllSupportedResolution(); mImagePreference = new IllustrationPreference(context); mDisplayObserver = new DisplayObserver(context); - ScreenResolutionController mController = - new ScreenResolutionController(context, "fragment"); - mResolutions = mController.getAllSupportedResolutions(); - mHighWidth = mController.getHighWidth(); - mFullWidth = mController.getFullWidth(); - mScreenResolutionSummaries = - new String[] { - mHighWidth + " x " + mController.getHighHeight(), - mFullWidth + " x " + mController.getFullHeight() - }; } @Override @@ -137,6 +133,16 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment { return candidates; } + /** Get all supported resolutions on the device. */ + private Set getAllSupportedResolution() { + Set resolutions = new HashSet<>(); + for (Display.Mode mode : mDefaultDisplay.getSupportedModes()) { + resolutions.add(new Point(mode.getPhysicalWidth(), mode.getPhysicalHeight())); + } + + return resolutions; + } + /** Get prefer display mode. */ private Display.Mode getPreferMode(int width) { for (Point resolution : mResolutions) { @@ -171,7 +177,6 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment { try { /** Apply the resolution change. */ - Log.i(TAG, "setUserPreferredDisplayMode: " + mode); mDefaultDisplay.setUserPreferredDisplayMode(mode); } catch (Exception e) { Log.e(TAG, "setUserPreferredDisplayMode() failed", e); @@ -189,19 +194,16 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment { /** Get the key corresponding to the resolution. */ @VisibleForTesting String getKeyForResolution(int width) { - return width == mHighWidth - ? mScreenResolutionOptions[ScreenResolutionController.HIGHRESOLUTION_IDX] - : width == mFullWidth - ? mScreenResolutionOptions[ScreenResolutionController.FULLRESOLUTION_IDX] - : null; + return width == FHD_WIDTH + ? mScreenResolutionOptions[FHD_INDEX] + : width == QHD_WIDTH ? mScreenResolutionOptions[QHD_INDEX] : null; } /** Get the width corresponding to the resolution key. */ int getWidthForResoluitonKey(String key) { - return mScreenResolutionOptions[ScreenResolutionController.HIGHRESOLUTION_IDX].equals(key) - ? mHighWidth - : mScreenResolutionOptions[ScreenResolutionController.FULLRESOLUTION_IDX].equals( - key) ? mFullWidth : -1; + return mScreenResolutionOptions[FHD_INDEX].equals(key) + ? FHD_WIDTH + : mScreenResolutionOptions[QHD_INDEX].equals(key) ? QHD_WIDTH : -1; } @Override @@ -246,11 +248,9 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment { private void updateIllustrationImage(IllustrationPreference preference) { String key = getDefaultKey(); - if (TextUtils.equals( - mScreenResolutionOptions[ScreenResolutionController.HIGHRESOLUTION_IDX], key)) { + if (TextUtils.equals(mScreenResolutionOptions[FHD_INDEX], key)) { preference.setLottieAnimationResId(R.drawable.screen_resolution_1080p); - } else if (TextUtils.equals( - mScreenResolutionOptions[ScreenResolutionController.FULLRESOLUTION_IDX], key)) { + } else if (TextUtils.equals(mScreenResolutionOptions[QHD_INDEX], key)) { preference.setLottieAnimationResId(R.drawable.screen_resolution_1440p); } } @@ -407,8 +407,6 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment { return false; } - Log.i(TAG, - "resolution changed from " + mPreviousWidth.get() + " to " + getCurrentWidth()); return true; } } From 83fce8a0aa2f22f2f75a9db29994340b9b23e816 Mon Sep 17 00:00:00 2001 From: Grace Cheng Date: Thu, 19 Jan 2023 00:31:26 +0000 Subject: [PATCH 4/8] Prevents retrying fingerprint after lockout reset if user exits FingerprintSettings Fingerprint auth is active on the enrolled fingerprints page to indicate to users which fingers are enrolled. Tapping 5x with unenrolled finger on this page sends FINGERPRINT_ERROR_LOCKOUT, which posts a mFingerprintLockoutReset runnable to retry fingerprint auth after LOCKOUT_DURATION. If a user turns off their device and unlocks via pin/pattern/password, this resets lockout, triggering the runnable and causing fingerprint auth to be retried upon device entry. This change prevents this by removing the runnable callback in FingerprintSettingsFragment#onPause. Test: Navigate to enrolled fingerprints page (Settings > Security > Face & Fingerprint Unlock > Fingerprint Unlock). Tap 5x with unenrolled finger, then turn off screen and unlock via pin/pattern/password. Ensure indicator does not appear upon device entry. Fixes: 261366312 Change-Id: I4190669286461e9ce884685a9929313da953476d --- .../settings/biometrics/fingerprint/FingerprintSettings.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java index 2721c773af2..a1d84da2176 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java @@ -641,6 +641,7 @@ public class FingerprintSettings extends SubSettings { if (mAuthenticateSidecar != null) { mAuthenticateSidecar.setListener(null); mAuthenticateSidecar.stopAuthentication(); + mHandler.removeCallbacks(mFingerprintLockoutReset); } } From 967203de772686716af072206aee173b29d1d033 Mon Sep 17 00:00:00 2001 From: Hao Dong Date: Thu, 15 Dec 2022 16:04:43 +0000 Subject: [PATCH 5/8] Fix UI bugs for udfps enroll in settings. This CL fixes four bugs for SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS feature: 1. When the udfps view is big enough (i.e. landscape mode on panther), the progress bar's bottom is clipped by the footer button. - Sets the footer button width WRAP_CONTENT and removes the space view to make the footer button short enough, not hiding the udfps view progress bar. 2. When both text size and display icon size are big enough on the portrait mode, the udfps view's position is wrong because its parent views are longer than the screen and scrollable. - Add addOnDrawListener() on udfps view's parent view, whenever it's changed, recalculate the margins of udfps view to make sure it's aligned with the sensor's position. 3. When the finger is down on the screen and the lighting circle on the sensor is shown, the fingerprint icon is not hidden. - Propagates FingerprintManager#onPointerDown and #onPointerUp to UdfpsEnrollView and hide/show fingerprint drawable accordingly. 4. When rotating the screen, fingerprint location is not right because UdfpsEnrollHelper is recreated. - Makes UdfpsEnrollHelper a fragment and call setRetainInstance(true) to keep it even though the configuration is changed. Test: manually tested on device: Turn this flag on via adb command adb shell setprop sys.fflag.override.settings_show_udfps_enroll_in_settings true Bug: 260617060 Change-Id: I15ffde6455cab7e9d4a394349ec39e72df5b2911 --- .../biometrics/BiometricEnrollSidecar.java | 50 ++++++++++++++++ .../FingerprintEnrollEnrolling.java | 45 ++++++++++++++- .../fingerprint/FingerprintEnrollSidecar.java | 10 ++++ .../fingerprint/FingerprintUpdater.java | 10 ++++ .../fingerprint/UdfpsEnrollHelper.java | 34 ++++++++++- .../fingerprint/UdfpsEnrollView.java | 57 +++++++++++++------ 6 files changed, 185 insertions(+), 21 deletions(-) diff --git a/src/com/android/settings/biometrics/BiometricEnrollSidecar.java b/src/com/android/settings/biometrics/BiometricEnrollSidecar.java index 9a89f7a349f..97d46a420e3 100644 --- a/src/com/android/settings/biometrics/BiometricEnrollSidecar.java +++ b/src/com/android/settings/biometrics/BiometricEnrollSidecar.java @@ -45,6 +45,14 @@ public abstract class BiometricEnrollSidecar extends InstrumentedFragment { * @param isAcquiredGood whether the fingerprint image was good. */ default void onAcquired(boolean isAcquiredGood) { } + /** + * Called when a pointer down event has occurred. + */ + default void onPointerDown(int sensorId) { } + /** + * Called when a pointer up event has occurred. + */ + default void onPointerUp(int sensorId) { } } private int mEnrollmentSteps = -1; @@ -118,6 +126,32 @@ public abstract class BiometricEnrollSidecar extends InstrumentedFragment { } } + private class QueuedPointerDown extends QueuedEvent { + private final int sensorId; + + public QueuedPointerDown(int sensorId) { + this.sensorId = sensorId; + } + + @Override + public void send(Listener listener) { + listener.onPointerDown(sensorId); + } + } + + private class QueuedPointerUp extends QueuedEvent { + private final int sensorId; + + public QueuedPointerUp(int sensorId) { + this.sensorId = sensorId; + } + + @Override + public void send(Listener listener) { + listener.onPointerUp(sensorId); + } + } + private final Runnable mTimeoutRunnable = new Runnable() { @Override public void run() { @@ -215,6 +249,22 @@ public abstract class BiometricEnrollSidecar extends InstrumentedFragment { } } + protected void onPointerDown(int sensorId) { + if (mListener != null) { + mListener.onPointerDown(sensorId); + } else { + mQueuedEvents.add(new QueuedPointerDown(sensorId)); + } + } + + protected void onPointerUp(int sensorId) { + if (mListener != null) { + mListener.onPointerUp(sensorId); + } else { + mQueuedEvents.add(new QueuedPointerUp(sensorId)); + } + } + public void setListener(Listener listener) { mListener = listener; if (mListener != null) { diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java index 60959e399a3..139a18fa376 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java @@ -82,6 +82,7 @@ import com.airbnb.lottie.LottieAnimationView; import com.airbnb.lottie.LottieCompositionFactory; import com.airbnb.lottie.LottieProperty; import com.airbnb.lottie.model.KeyPath; +import com.google.android.setupcompat.template.FooterActionButton; import com.google.android.setupcompat.template.FooterBarMixin; import com.google.android.setupcompat.template.FooterButton; import com.google.android.setupcompat.util.WizardManagerHelper; @@ -101,6 +102,7 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { private static final String TAG = "FingerprintEnrollEnrolling"; static final String TAG_SIDECAR = "sidecar"; + static final String TAG_UDFPS_HELPER = "udfps_helper"; static final String KEY_STATE_CANCELED = "is_canceled"; static final String KEY_STATE_PREVIOUS_ROTATION = "previous_rotation"; @@ -353,6 +355,24 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { .build() ); + if (FeatureFlagUtils.isEnabled(getApplicationContext(), + FeatureFlagUtils.SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS)) { + // Remove the space view and make the width of footer button container WRAP_CONTENT + // to avoid hiding the udfps view progress bar bottom. + final LinearLayout buttonContainer = mFooterBarMixin.getButtonContainer(); + View spaceView = null; + for (int i = 0; i < buttonContainer.getChildCount(); i++) { + if (!(buttonContainer.getChildAt(i) instanceof FooterActionButton)) { + spaceView = buttonContainer.getChildAt(i); + break; + } + } + if (spaceView != null) { + spaceView.setVisibility(View.GONE); + buttonContainer.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT; + } + } + final LayerDrawable fingerprintDrawable = mProgressBar != null ? (LayerDrawable) mProgressBar.getBackground() : null; if (fingerprintDrawable != null) { @@ -867,6 +887,20 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { } } + @Override + public void onPointerDown(int sensorId) { + if (mUdfpsEnrollHelper != null) { + mUdfpsEnrollHelper.onPointerDown(sensorId); + } + } + + @Override + public void onPointerUp(int sensorId) { + if (mUdfpsEnrollHelper != null) { + mUdfpsEnrollHelper.onPointerUp(sensorId); + } + } + private void updateProgress(boolean animate) { if (mSidecar == null || !mSidecar.isEnrolling()) { Log.d(TAG, "Enrollment not started yet"); @@ -1195,7 +1229,16 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { udfpsProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL); udfpsEnrollView.setOverlayParams(params); - mUdfpsEnrollHelper = new UdfpsEnrollHelper(getApplicationContext(), mFingerprintManager); + + mUdfpsEnrollHelper = (UdfpsEnrollHelper) getSupportFragmentManager().findFragmentByTag( + FingerprintEnrollEnrolling.TAG_UDFPS_HELPER); + if (mUdfpsEnrollHelper == null) { + mUdfpsEnrollHelper = new UdfpsEnrollHelper(getApplicationContext(), + mFingerprintManager); + getSupportFragmentManager().beginTransaction() + .add(mUdfpsEnrollHelper, FingerprintEnrollEnrolling.TAG_UDFPS_HELPER) + .commitAllowingStateLoss(); + } udfpsEnrollView.setEnrollHelper(mUdfpsEnrollHelper); return udfpsEnrollView; diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollSidecar.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollSidecar.java index 3adfc28b113..5d04cd6c5ed 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollSidecar.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollSidecar.java @@ -122,6 +122,16 @@ public class FingerprintEnrollSidecar extends BiometricEnrollSidecar { public void onEnrollmentError(int errMsgId, CharSequence errString) { FingerprintEnrollSidecar.super.onEnrollmentError(errMsgId, errString); } + + @Override + public void onPointerDown(int sensorId) { + FingerprintEnrollSidecar.super.onPointerDown(sensorId); + } + + @Override + public void onPointerUp(int sensorId) { + FingerprintEnrollSidecar.super.onPointerUp(sensorId); + } }; @Override diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintUpdater.java b/src/com/android/settings/biometrics/fingerprint/FingerprintUpdater.java index 25689374da8..36325a7b975 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintUpdater.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintUpdater.java @@ -96,6 +96,16 @@ public class FingerprintUpdater { public void onAcquired(boolean isAcquiredGood) { mCallback.onAcquired(isAcquiredGood); } + + @Override + public void onPointerDown(int sensorId) { + mCallback.onPointerDown(sensorId); + } + + @Override + public void onPointerUp(int sensorId) { + mCallback.onPointerUp(sensorId); + } } /** diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollHelper.java b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollHelper.java index f42b8eca2fc..f7f138cbabd 100644 --- a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollHelper.java +++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollHelper.java @@ -22,19 +22,22 @@ import android.content.Context; import android.graphics.PointF; import android.hardware.fingerprint.FingerprintManager; import android.os.Build; +import android.os.Bundle; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; import android.util.TypedValue; import android.view.accessibility.AccessibilityManager; +import com.android.settings.core.InstrumentedFragment; + import java.util.ArrayList; import java.util.List; /** * Helps keep track of enrollment state and animates the progress bar accordingly. */ -public class UdfpsEnrollHelper { +public class UdfpsEnrollHelper extends InstrumentedFragment { private static final String TAG = "UdfpsEnrollHelper"; private static final String SCALE_OVERRIDE = @@ -50,6 +53,10 @@ public class UdfpsEnrollHelper { void onEnrollmentHelp(int remaining, int totalSteps); void onAcquired(boolean animateIfLastStepGood); + + void onPointerDown(int sensorId); + + void onPointerUp(int sensorId); } @NonNull @@ -124,6 +131,17 @@ public class UdfpsEnrollHelper { } } + @Override + public int getMetricsCategory() { + return 0; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + } + void onEnrollmentProgress(int totalSteps, int remaining) { if (mTotalSteps == -1) { mTotalSteps = totalSteps; @@ -144,7 +162,7 @@ public class UdfpsEnrollHelper { } void onEnrollmentHelp() { - if (mListener != null && mTotalSteps != -1) { + if (mListener != null) { mListener.onEnrollmentHelp(mRemainingSteps, mTotalSteps); } } @@ -155,6 +173,18 @@ public class UdfpsEnrollHelper { } } + void onPointerDown(int sensorId) { + if (mListener != null) { + mListener.onPointerDown(sensorId); + } + } + + void onPointerUp(int sensorId) { + if (mListener != null) { + mListener.onPointerUp(sensorId); + } + } + void setListener(UdfpsEnrollHelper.Listener listener) { mListener = listener; diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollView.java b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollView.java index 3e3fd0e436d..3d77f0e8b63 100644 --- a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollView.java +++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollView.java @@ -88,10 +88,22 @@ public class UdfpsEnrollView extends FrameLayout implements UdfpsEnrollHelper.Li @Override public void onAcquired(boolean animateIfLastStepGood) { mHandler.post(() -> { + onFingerUp(); if (animateIfLastStepGood) mFingerprintProgressDrawable.onLastStepAcquired(); }); } + @Override + public void onPointerDown(int sensorId) { + onFingerDown(); + } + + + @Override + public void onPointerUp(int sensorId) { + onFingerUp(); + } + void setOverlayParams(UdfpsOverlayParams params) { mOverlayParams = params; @@ -99,7 +111,7 @@ public class UdfpsEnrollView extends FrameLayout implements UdfpsEnrollHelper.Li mProgressBarRadius = (int) (mOverlayParams.getScaleFactor() * getContext().getResources().getInteger( R.integer.config_udfpsEnrollProgressBar)); - mSensorRect = mOverlayParams.getSensorBounds(); + mSensorRect = new Rect(mOverlayParams.getSensorBounds()); onSensorRectUpdated(); }); @@ -123,7 +135,7 @@ public class UdfpsEnrollView extends FrameLayout implements UdfpsEnrollHelper.Li private void updateDimensions() { // Original sensorBounds assume portrait mode. - Rect rotatedBounds = mOverlayParams.getSensorBounds(); + final Rect rotatedBounds = mOverlayParams.getSensorBounds(); int rotation = mOverlayParams.getRotation(); if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) { RotationUtils.rotateBounds( @@ -137,33 +149,42 @@ public class UdfpsEnrollView extends FrameLayout implements UdfpsEnrollHelper.Li // Use parent view's and rotatedBound's absolute coordinates to decide the margins of // UdfpsEnrollView, so that its center keeps consistent with sensor rect's. ViewGroup parentView = (ViewGroup) getParent(); - int[] coords = parentView.getLocationOnScreen(); - int parentLeft = coords[0]; - int parentTop = coords[1]; - int parentRight = parentLeft + parentView.getWidth(); - int parentBottom = parentTop + parentView.getHeight(); MarginLayoutParams marginLayoutParams = (MarginLayoutParams) getLayoutParams(); FrameLayout.LayoutParams params = (LayoutParams) getLayoutParams(); - - switch (rotation) { - case Surface.ROTATION_0: - case Surface.ROTATION_180: + if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) { + parentView.getViewTreeObserver().addOnDrawListener(() -> { + final int[] coords = parentView.getLocationOnScreen(); + final int parentLeft = coords[0]; + final int parentTop = coords[1]; + final int parentRight = parentLeft + parentView.getWidth(); params.gravity = Gravity.RIGHT | Gravity.TOP; - marginLayoutParams.rightMargin = parentRight - rotatedBounds.right - getPaddingX(); - marginLayoutParams.topMargin = rotatedBounds.top - parentTop - getPaddingY(); - break; - case Surface.ROTATION_90: + final int rightMargin = parentRight - rotatedBounds.right - getPaddingX(); + final int topMargin = rotatedBounds.top - parentTop - getPaddingY(); + if (marginLayoutParams.rightMargin == rightMargin + && marginLayoutParams.topMargin == topMargin) { + return; + } + marginLayoutParams.rightMargin = rightMargin; + marginLayoutParams.topMargin = topMargin; + setLayoutParams(params); + }); + } else { + final int[] coords = parentView.getLocationOnScreen(); + final int parentLeft = coords[0]; + final int parentTop = coords[1]; + final int parentRight = parentLeft + parentView.getWidth(); + final int parentBottom = parentTop + parentView.getHeight(); + if (rotation == Surface.ROTATION_90) { params.gravity = Gravity.RIGHT | Gravity.BOTTOM; marginLayoutParams.rightMargin = parentRight - rotatedBounds.right - getPaddingX(); marginLayoutParams.bottomMargin = parentBottom - rotatedBounds.bottom - getPaddingY(); - break; - case Surface.ROTATION_270: + } else if (rotation == Surface.ROTATION_270) { params.gravity = Gravity.LEFT | Gravity.BOTTOM; marginLayoutParams.leftMargin = rotatedBounds.left - parentLeft - getPaddingX(); marginLayoutParams.bottomMargin = parentBottom - rotatedBounds.bottom - getPaddingY(); - break; + } } params.height = rotatedBounds.height() + 2 * getPaddingX(); From fd25f404f8a809329fb1613e63790a06ae2c1be1 Mon Sep 17 00:00:00 2001 From: Zaiyue Xue Date: Thu, 19 Jan 2023 12:10:59 +0800 Subject: [PATCH 6/8] Fix the build failure in studiow. The b/21010898 changed the security_settings_sfps_enroll_find_sensor_message in branch tm-qpr. But in the branch master security_settings_sfps_enroll_find_sensor_message has been moved from res to res-product. The merge cl ag/21024400 should update the message in the new place instead of adding it back to res. Test: manual Change-Id: I9ccc08be15631bff5e70ad146ba3761689206658 --- res-product/values/strings.xml | 6 +++--- res/values/strings.xml | 6 ------ 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/res-product/values/strings.xml b/res-product/values/strings.xml index c8ec5bd29a6..d549d668f4e 100644 --- a/res-product/values/strings.xml +++ b/res-product/values/strings.xml @@ -234,11 +234,11 @@ Device protection features won\u2019t be turned on. You won\u2019t be able to prevent others from using this phone if it\u2019s lost or stolen. - The fingerprint sensor is on the power button. It’s the flat button next to the raised volume button on the edge of the tablet.\n\nPressing the power button turns off the screen. + The fingerprint sensor is on the power button. It’s the flat button next to the raised volume button on the edge of the tablet. - The fingerprint sensor is on the power button. It’s the flat button next to the raised volume button on the edge of the device.\n\nPressing the power button turns off the screen. + The fingerprint sensor is on the power button. It’s the flat button next to the raised volume button on the edge of the device. - The fingerprint sensor is on the power button. It’s the flat button next to the raised volume button on the edge of the phone.\n\nPressing the power button turns off the screen. + The fingerprint sensor is on the power button. It’s the flat button next to the raised volume button on the edge of the phone. Now you can use your fingerprint to unlock your tablet or verify it\u2019s you, like when you sign in to apps or approve a purchase diff --git a/res/values/strings.xml b/res/values/strings.xml index 0dc78dc3d1f..3fec86b09cc 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -826,12 +826,6 @@ How to set up your fingerprint It\u2019s on the back of your phone. Use your index finger. - - The fingerprint sensor is on the power button. It’s the flat button next to the raised volume button on the edge of the tablet. - - The fingerprint sensor is on the power button. It’s the flat button next to the raised volume button on the edge of the device. - - The fingerprint sensor is on the power button. It’s the flat button next to the raised volume button on the edge of the phone. The fingerprint sensor is on your screen. You\u2019ll capture your fingerprint on the next screen. From 4c3532f5ed5c47ec61ebe6cab2237d2ac2a45727 Mon Sep 17 00:00:00 2001 From: Zaiyue Xue Date: Thu, 19 Jan 2023 15:41:01 +0800 Subject: [PATCH 7/8] Fix b/265364380: Icon for Other is not showing up on light theme screenshots after fix: https://screenshot.googleplex.com/4Hm2fo44jVeuLWQ.png https://screenshot.googleplex.com/6ZpW34AvXu9Hfvm.png Bug: 265364380 Fix: 265364380 Test: manual Change-Id: I2b73facba2704f4e01bd1e2d4f8e6f09dbca6286 --- .../ic_settings_battery_usage_others.xml | 35 +++++++++++++++++++ .../batteryusage/BatteryDiffEntry.java | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 res/drawable/ic_settings_battery_usage_others.xml diff --git a/res/drawable/ic_settings_battery_usage_others.xml b/res/drawable/ic_settings_battery_usage_others.xml new file mode 100644 index 00000000000..a7abfe0615d --- /dev/null +++ b/res/drawable/ic_settings_battery_usage_others.xml @@ -0,0 +1,35 @@ + + + + + + + + diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java index 9551404e2d2..d53afdf5b98 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java @@ -487,7 +487,7 @@ public class BatteryDiffEntry { @Override public Drawable getAppIcon() { - return mContext.getDrawable(R.drawable.ic_settings_ethernet); + return mContext.getDrawable(R.drawable.ic_settings_battery_usage_others); } @Override From 1732c2270603a861093f38a8a80878808109eb37 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Thu, 19 Jan 2023 17:37:13 +0800 Subject: [PATCH 8/8] Add unit test for Picture In Picture Bug: 260660819 Test: Unit test Change-Id: I64917bf58082b49ffcfbb4926e8ccd26d2e7c5f4 --- .../app/specialaccess/PictureInPictureTest.kt | 178 ++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 tests/spa_unit/src/com/android/settings/spa/app/specialaccess/PictureInPictureTest.kt diff --git a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/PictureInPictureTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/PictureInPictureTest.kt new file mode 100644 index 00000000000..5e2b8c6e4cd --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/PictureInPictureTest.kt @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.spa.app.specialaccess + +import android.app.AppOpsManager +import android.content.Context +import android.content.pm.ActivityInfo +import android.content.pm.ApplicationInfo +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.content.pm.PackageManager.PackageInfoFlags +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.R +import com.android.settingslib.spaprivileged.model.app.AppOpsController +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.eq +import org.mockito.Spy +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.mockito.Mockito.`when` as whenever + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +class PictureInPictureTest { + @get:Rule + val mockito: MockitoRule = MockitoJUnit.rule() + + @Spy + private val context: Context = ApplicationProvider.getApplicationContext() + + @Mock + private lateinit var packageManager: PackageManager + + private lateinit var listModel: PictureInPictureListModel + + @Before + fun setUp() { + whenever(context.packageManager).thenReturn(packageManager) + whenever(packageManager.getInstalledPackagesAsUser(any(), anyInt())) + .thenReturn(emptyList()) + listModel = PictureInPictureListModel(context) + } + + @Test + fun pageTitleResId() { + assertThat(listModel.pageTitleResId).isEqualTo(R.string.picture_in_picture_title) + } + + @Test + fun switchTitleResId() { + assertThat(listModel.switchTitleResId) + .isEqualTo(R.string.picture_in_picture_app_detail_switch) + } + + @Test + fun footerResId() { + assertThat(listModel.footerResId).isEqualTo(R.string.picture_in_picture_app_detail_summary) + } + + @Test + fun transform() = runTest { + whenever(packageManager.getInstalledPackagesAsUser(any(), anyInt())) + .thenReturn(listOf(PICTURE_IN_PICTURE_PACKAGE_INFO)) + + val recordListFlow = listModel.transform( + userIdFlow = flowOf(USER_ID), + appListFlow = flowOf(listOf(PICTURE_IN_PICTURE_APP)), + ) + + val recordList = recordListFlow.first() + assertThat(recordList).hasSize(1) + val record = recordList[0] + assertThat(record.app).isSameInstanceAs(PICTURE_IN_PICTURE_APP) + assertThat(record.isSupport).isTrue() + } + + @Test + fun transformItem() { + whenever( + packageManager.getPackageInfoAsUser( + eq(PICTURE_IN_PICTURE_PACKAGE_NAME), any(), eq(USER_ID) + ) + ).thenReturn(PICTURE_IN_PICTURE_PACKAGE_INFO) + + val record = listModel.transformItem(PICTURE_IN_PICTURE_APP) + + assertThat(record.app).isSameInstanceAs(PICTURE_IN_PICTURE_APP) + assertThat(record.isSupport).isTrue() + } + + @Test + fun filter_isSupport() = runTest { + val record = createRecord(isSupport = true) + + val recordListFlow = listModel.filter(flowOf(USER_ID), flowOf(listOf(record))) + + val recordList = recordListFlow.first() + assertThat(recordList).hasSize(1) + } + + @Test + fun filter_notSupport() = runTest { + val record = createRecord(isSupport = false) + + val recordListFlow = listModel.filter(flowOf(USER_ID), flowOf(listOf(record))) + + val recordList = recordListFlow.first() + assertThat(recordList).isEmpty() + } + + @Test + fun isChangeable_isSupport() { + val record = createRecord(isSupport = true) + + val isChangeable = listModel.isChangeable(record) + + assertThat(isChangeable).isTrue() + } + + @Test + fun isChangeable_notSupport() { + val record = createRecord(isSupport = false) + + val isChangeable = listModel.isChangeable(record) + + assertThat(isChangeable).isFalse() + } + + private fun createRecord(isSupport: Boolean) = PictureInPictureRecord( + app = PICTURE_IN_PICTURE_APP, + isSupport = isSupport, + appOpsController = AppOpsController( + context = context, + app = PICTURE_IN_PICTURE_APP, + op = AppOpsManager.OP_PICTURE_IN_PICTURE, + ), + ) + + private companion object { + const val USER_ID = 0 + const val PICTURE_IN_PICTURE_PACKAGE_NAME = "picture.in.picture.package.name" + val PICTURE_IN_PICTURE_APP = ApplicationInfo().apply { + packageName = PICTURE_IN_PICTURE_PACKAGE_NAME + } + val PICTURE_IN_PICTURE_PACKAGE_INFO = PackageInfo().apply { + packageName = PICTURE_IN_PICTURE_PACKAGE_NAME + activities = arrayOf(ActivityInfo().apply { + flags = ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE + }) + } + } +} \ No newline at end of file