From 4c1dd91695c07c4c42da18eef2d323b3710ba778 Mon Sep 17 00:00:00 2001 From: Weng Su Date: Thu, 18 May 2023 04:07:05 +0800 Subject: [PATCH 01/11] Restrict Printing Settings - Don't show Printing Settings if the user is restricted by UserManager.DISALLOW_PRINTING Bug: 269122009 Test: manual test atest -c PrintSettingsFragmentTest Change-Id: I15db778ebc4de3c8a76505043fab1ee02db3f290 --- .../settings/print/PrintSettingsFragment.java | 70 ++++++++++--- .../ProfileSettingsPreferenceFragment.java | 8 +- .../print/PrintSettingsFragmentTest.java | 97 +++++++++++++++++++ 3 files changed, 159 insertions(+), 16 deletions(-) create mode 100644 tests/unit/src/com/android/settings/print/PrintSettingsFragmentTest.java diff --git a/src/com/android/settings/print/PrintSettingsFragment.java b/src/com/android/settings/print/PrintSettingsFragment.java index ed21b6f7f92..cd80998170c 100644 --- a/src/com/android/settings/print/PrintSettingsFragment.java +++ b/src/com/android/settings/print/PrintSettingsFragment.java @@ -28,6 +28,7 @@ import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; +import android.os.UserManager; import android.print.PrintJob; import android.print.PrintJobId; import android.print.PrintJobInfo; @@ -45,6 +46,7 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.TextView; +import androidx.annotation.VisibleForTesting; import androidx.loader.app.LoaderManager.LoaderCallbacks; import androidx.loader.content.AsyncTaskLoader; import androidx.loader.content.Loader; @@ -92,6 +94,22 @@ public class PrintSettingsFragment extends ProfileSettingsPreferenceFragment private PrintServicesController mPrintServicesController; private Button mAddNewServiceButton; + @VisibleForTesting + boolean mIsUiRestricted; + + public PrintSettingsFragment() { + super(UserManager.DISALLOW_PRINTING); + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.print_settings; + } @Override public int getMetricsCategory() { @@ -107,12 +125,19 @@ public class PrintSettingsFragment extends ProfileSettingsPreferenceFragment public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = super.onCreateView(inflater, container, savedInstanceState); - addPreferencesFromResource(R.xml.print_settings); + mIsUiRestricted = isUiRestricted(); + setupPreferences(); + return root; + } - mActivePrintJobsCategory = (PreferenceCategory) findPreference( - PRINT_JOBS_CATEGORY); - mPrintServicesCategory = (PreferenceCategory) findPreference( - PRINT_SERVICES_CATEGORY); + @VisibleForTesting + void setupPreferences() { + if (mIsUiRestricted) { + return; + } + + mActivePrintJobsCategory = (PreferenceCategory) findPreference(PRINT_JOBS_CATEGORY); + mPrintServicesCategory = (PreferenceCategory) findPreference(PRINT_SERVICES_CATEGORY); getPreferenceScreen().removePreference(mActivePrintJobsCategory); mPrintJobsController = new PrintJobsController(); @@ -120,20 +145,20 @@ public class PrintSettingsFragment extends ProfileSettingsPreferenceFragment mPrintServicesController = new PrintServicesController(); getLoaderManager().initLoader(LOADER_ID_PRINT_SERVICES, null, mPrintServicesController); - - return root; - } - - @Override - public void onStart() { - super.onStart(); - setHasOptionsMenu(true); - startSubSettingsIfNeeded(); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + setupEmptyViews(); + } + + @VisibleForTesting + void setupEmptyViews() { + if (mIsUiRestricted) { + return; + } + ViewGroup contentRoot = (ViewGroup) getListView().getParent(); View emptyView = getActivity().getLayoutInflater().inflate( R.layout.empty_print_state, contentRoot, false); @@ -152,6 +177,23 @@ public class PrintSettingsFragment extends ProfileSettingsPreferenceFragment setEmptyView(emptyView); } + @Override + public void onStart() { + super.onStart(); + startSettings(); + } + + @VisibleForTesting + void startSettings() { + if (mIsUiRestricted) { + getPreferenceScreen().removeAll(); + return; + } + + setHasOptionsMenu(true); + startSubSettingsIfNeeded(); + } + @Override protected String getIntentActionString() { return Settings.ACTION_PRINT_SETTINGS; diff --git a/src/com/android/settings/print/ProfileSettingsPreferenceFragment.java b/src/com/android/settings/print/ProfileSettingsPreferenceFragment.java index e41e1dad3a1..63b83f1bf34 100644 --- a/src/com/android/settings/print/ProfileSettingsPreferenceFragment.java +++ b/src/com/android/settings/print/ProfileSettingsPreferenceFragment.java @@ -27,13 +27,17 @@ import android.widget.AdapterView; import android.widget.Spinner; import com.android.settings.R; -import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.dashboard.RestrictedDashboardFragment; import com.android.settings.dashboard.profileselector.UserAdapter; /** * Base fragment class for per profile settings. */ -public abstract class ProfileSettingsPreferenceFragment extends SettingsPreferenceFragment { +public abstract class ProfileSettingsPreferenceFragment extends RestrictedDashboardFragment { + + public ProfileSettingsPreferenceFragment(String restrictionKey) { + super(restrictionKey); + } @Override public void onViewCreated(View view, Bundle savedInstanceState) { diff --git a/tests/unit/src/com/android/settings/print/PrintSettingsFragmentTest.java b/tests/unit/src/com/android/settings/print/PrintSettingsFragmentTest.java new file mode 100644 index 00000000000..c52c5bc7f28 --- /dev/null +++ b/tests/unit/src/com/android/settings/print/PrintSettingsFragmentTest.java @@ -0,0 +1,97 @@ +/* + * 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.print; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.os.Looper; +import android.view.View; + +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@RunWith(AndroidJUnit4.class) +public class PrintSettingsFragmentTest { + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Spy + private final Context mContext = ApplicationProvider.getApplicationContext(); + + private PrintSettingsFragment mFragment; + private PreferenceManager mPreferenceManager; + private PreferenceScreen mPreferenceScreen; + + @Before + public void setUp() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + mPreferenceManager = new PreferenceManager(mContext); + mPreferenceScreen = mPreferenceManager.createPreferenceScreen(mContext); + + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + mFragment = spy(new PrintSettingsFragment()); + doReturn(mPreferenceScreen).when(mFragment).getPreferenceScreen(); + }); + } + + @Test + public void setupPreferences_uiIsRestricted_doNotAddPreferences() { + mFragment.mIsUiRestricted = true; + + mFragment.setupPreferences(); + + verify(mFragment, never()).findPreference(any(CharSequence.class)); + } + + @Test + public void setupEmptyViews_uiIsRestricted_doNotSetEmptyView() { + mFragment.mIsUiRestricted = true; + + mFragment.setupEmptyViews(); + + verify(mFragment, never()).setEmptyView(any(View.class)); + } + + @Test + public void startSettings_uiIsRestricted_removeAllPreferences() { + mFragment.mIsUiRestricted = true; + + mFragment.startSettings(); + + assertThat(mPreferenceScreen.getPreferenceCount()).isEqualTo(0); + verify(mFragment, never()).setHasOptionsMenu(true); + } +} From 0183cfd81642626aaa2c9e0d8592f0382ccd1a33 Mon Sep 17 00:00:00 2001 From: Yuxin Hu Date: Sat, 20 May 2023 00:52:11 +0000 Subject: [PATCH 02/11] Fix "Enable ANGLE" switch behavior when Developer Option Switch is off Fix below bugs: 1) When developer option switch is turned off, "Enable ANGLE" switch is not disabled. Fixed it in GraphicsDriverEnableAngleAsSystemDriverController::update(). 2) When user turns off developer option switch, a restart is not always mandatory. However, we should ask user to restart the device is "Enable ANGLE" is forced to change due to developer option switch turning off. Fixed it in DevelopmentSettingsDashboardFragment::onSwitchChanged(). 3) When user turns off developer option switch and the reboot dialog pops up, user can dismiss the dialog by not clicking either POSITIVE_BUTTON or NEGATIVE_BUTTON. We should enforce user to click the button and disallow the option to dimiss the dialog by touching screen areas outside the window's bounds. Fixed it in DisableDevSettingsDialogFragment.java. Did a few clean-up and in GraphicsDriverEnableAngleAsSystemDriverController.java, and updated unit test to reflect new code behavior. Bug: b/270994705 Test: m; flash and device can boot. Test: atest -c GraphicsDriverEnableAngleAsSystemDriverControllerJUnitTest Test: atest GraphicsDriverEnableAngleAsSystemDriverControllerTest Test: atest SettingsRoboTests:FreeformWindowsPreferenceControllerTest Test: atest SettingsRoboTests:DesktopModePreferenceControllerTest Change-Id: I199b2fe59b6ad948b753793254c822a293d8b40d --- .../DevelopmentSettingsDashboardFragment.java | 7 ++- .../DisableDevSettingsDialogFragment.java | 4 ++ ...erEnableAngleAsSystemDriverController.java | 57 +++++++++++-------- ...ableAngleAsSystemDriverControllerTest.java | 3 + ...ngleAsSystemDriverControllerJUnitTest.java | 26 +++------ 5 files changed, 55 insertions(+), 42 deletions(-) diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index 87d8c17e307..f7be1aa47b1 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -354,13 +354,18 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra final NfcVerboseVendorLogPreferenceController nfcVerboseLogController = getDevelopmentOptionsController( NfcVerboseVendorLogPreferenceController.class); + final GraphicsDriverEnableAngleAsSystemDriverController enableAngleController = + getDevelopmentOptionsController( + GraphicsDriverEnableAngleAsSystemDriverController.class); // If hardware offload isn't default value, we must reboot after disable // developer options. Show a dialog for the user to confirm. if ((a2dpController == null || a2dpController.isDefaultValue()) && (leAudioController == null || leAudioController.isDefaultValue()) && (nfcSnoopLogController == null || nfcSnoopLogController.isDefaultValue()) && (nfcVerboseLogController == null - || nfcVerboseLogController.isDefaultValue())) { + || nfcVerboseLogController.isDefaultValue()) + && (enableAngleController == null + || enableAngleController.isDefaultValue())) { disableDeveloperOptions(); } else { DisableDevSettingsDialogFragment.show(this /* host */); diff --git a/src/com/android/settings/development/DisableDevSettingsDialogFragment.java b/src/com/android/settings/development/DisableDevSettingsDialogFragment.java index 5db2ed4281b..224768b0561 100644 --- a/src/com/android/settings/development/DisableDevSettingsDialogFragment.java +++ b/src/com/android/settings/development/DisableDevSettingsDialogFragment.java @@ -45,6 +45,10 @@ public class DisableDevSettingsDialogFragment extends InstrumentedDialogFragment public static void show(DevelopmentSettingsDashboardFragment host) { final DisableDevSettingsDialogFragment dialog = new DisableDevSettingsDialogFragment(); dialog.setTargetFragment(host, 0 /* requestCode */); + // We need to handle data changes and switch state based on which button user clicks, + // therefore we should enforce user to click one of the buttons + // by disallowing dialog dismiss through tapping outside of dialog bounds. + dialog.setCancelable(false); final FragmentManager manager = host.getActivity().getSupportFragmentManager(); dialog.show(manager, TAG); } diff --git a/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverController.java b/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverController.java index 1a065a96aaa..b3af95ec0cc 100644 --- a/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverController.java +++ b/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverController.java @@ -81,6 +81,11 @@ public class GraphicsDriverEnableAngleAsSystemDriverController this(context, fragment, new Injector()); } + private boolean isAngleSupported() { + return TextUtils.equals( + mSystemProperties.get(PROPERTY_RO_GFX_ANGLE_SUPPORTED, ""), "true"); + } + @VisibleForTesting GraphicsDriverEnableAngleAsSystemDriverController( Context context, DevelopmentSettingsDashboardFragment fragment, Injector injector) { @@ -118,38 +123,44 @@ public class GraphicsDriverEnableAngleAsSystemDriverController this); } - @Override - public void updateState(Preference preference) { - // set switch on if "persist.graphics.egl" is "angle" and angle is built in /vendor - // set switch off otherwise. + /** Return the default value of "persist.graphics.egl" */ + public boolean isDefaultValue() { + if (!isAngleSupported()) { + return true; + } + final String currentGlesDriver = mSystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL, ""); - final boolean isAngle = TextUtils.equals(ANGLE_DRIVER_SUFFIX, currentGlesDriver); - final boolean isAngleSupported = - TextUtils.equals( - mSystemProperties.get(PROPERTY_RO_GFX_ANGLE_SUPPORTED, ""), "true"); - ((SwitchPreference) mPreference).setChecked(isAngle && isAngleSupported); - ((SwitchPreference) mPreference).setEnabled(isAngleSupported); + // default value of "persist.graphics.egl" is "" + return TextUtils.isEmpty(currentGlesDriver); } @Override - protected void onDeveloperOptionsSwitchEnabled() { - // only enable the switch if ro.gfx.angle.supported is true - // we use ro.gfx.angle.supported to indicate if ANGLE libs are installed under /vendor - final boolean isAngleSupported = - TextUtils.equals( - mSystemProperties.get(PROPERTY_RO_GFX_ANGLE_SUPPORTED, ""), "true"); - ((SwitchPreference) mPreference).setEnabled(isAngleSupported); + public void updateState(Preference preference) { + super.updateState(preference); + if (isAngleSupported()) { + // set switch on if "persist.graphics.egl" is "angle" and angle is built in /vendor + // set switch off otherwise. + final String currentGlesDriver = + mSystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL, ""); + final boolean isAngle = TextUtils.equals(ANGLE_DRIVER_SUFFIX, currentGlesDriver); + ((SwitchPreference) mPreference).setChecked(isAngle); + } else { + mPreference.setEnabled(false); + ((SwitchPreference) mPreference).setChecked(false); + } } @Override protected void onDeveloperOptionsSwitchDisabled() { - // 1) set the persist.graphics.egl empty string - GraphicsEnvironment.getInstance().toggleAngleAsSystemDriver(false); - // 2) reset the switch - ((SwitchPreference) mPreference).setChecked(false); - // 3) disable switch - ((SwitchPreference) mPreference).setEnabled(false); + // 1) disable the switch + super.onDeveloperOptionsSwitchDisabled(); + if (isAngleSupported()) { + // 2) set the persist.graphics.egl empty string + GraphicsEnvironment.getInstance().toggleAngleAsSystemDriver(false); + // 3) reset the switch + ((SwitchPreference) mPreference).setChecked(false); + } } void toggleSwitchBack() { diff --git a/tests/robotests/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverControllerTest.java b/tests/robotests/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverControllerTest.java index de380c40c8e..9210b870d77 100644 --- a/tests/robotests/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverControllerTest.java +++ b/tests/robotests/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverControllerTest.java @@ -141,6 +141,7 @@ public class GraphicsDriverEnableAngleAsSystemDriverControllerTest { @Test public void onDeveloperOptionSwitchDisabled_shouldDisableAngleAsSystemDriver() { + ShadowSystemProperties.override(PROPERTY_RO_GFX_ANGLE_SUPPORTED, "true"); mController.onDeveloperOptionsSwitchDisabled(); final String systemEGLDriver = SystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL); assertThat(systemEGLDriver).isEqualTo(""); @@ -148,12 +149,14 @@ public class GraphicsDriverEnableAngleAsSystemDriverControllerTest { @Test public void onDeveloperOptionSwitchDisabled_preferenceShouldNotBeChecked() { + ShadowSystemProperties.override(PROPERTY_RO_GFX_ANGLE_SUPPORTED, "true"); mController.onDeveloperOptionsSwitchDisabled(); verify(mPreference).setChecked(false); } @Test public void onDeveloperOptionsSwitchDisabled_preferenceShouldNotBeEnabled() { + ShadowSystemProperties.override(PROPERTY_RO_GFX_ANGLE_SUPPORTED, "true"); mController.onDeveloperOptionsSwitchDisabled(); verify(mPreference).setEnabled(false); } diff --git a/tests/unit/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverControllerJUnitTest.java b/tests/unit/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverControllerJUnitTest.java index c23292ac54a..ae35431fef7 100644 --- a/tests/unit/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverControllerJUnitTest.java +++ b/tests/unit/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverControllerJUnitTest.java @@ -57,7 +57,7 @@ public class GraphicsDriverEnableAngleAsSystemDriverControllerJUnitTest { private GraphicsDriverEnableAngleAsSystemDriverController mController; // Signal to wait for SystemProperty values changed - private class PropertyChangeSignal { + private static class PropertyChangeSignal { private CountDownLatch mCountDownLatch; private Runnable mCountDownJob; @@ -217,23 +217,7 @@ public class GraphicsDriverEnableAngleAsSystemDriverControllerJUnitTest { } @Test - public void onDeveloperOptionSwitchEnabled_angleSupported_PreferenceShouldEnabled() { - when(mSystemPropertiesMock.get(eq(PROPERTY_RO_GFX_ANGLE_SUPPORTED), any())) - .thenReturn("true"); - mController.onDeveloperOptionsSwitchEnabled(); - assertThat(mPreference.isEnabled()).isTrue(); - } - - @Test - public void onDeveloperOptionSwitchEnabled_angleNotSupported_PrefenceShouldDisabled() { - when(mSystemPropertiesMock.get(eq(PROPERTY_RO_GFX_ANGLE_SUPPORTED), any())) - .thenReturn("false"); - mController.onDeveloperOptionsSwitchEnabled(); - assertThat(mPreference.isEnabled()).isFalse(); - } - - @Test - public void onDeveloperOptionSwitchDisabled_angleIsNotSystemGLESDriver() { + public void onDeveloperOptionSwitchDisabled_angleShouldNotBeSystemGLESDriver() { // Add a callback when SystemProperty changes. // This allows the thread to wait until // GpuService::toggleAngleAsSystemDriver() updates the persist.graphics.egl. @@ -242,6 +226,8 @@ public class GraphicsDriverEnableAngleAsSystemDriverControllerJUnitTest { // Test that onDeveloperOptionSwitchDisabled, // persist.graphics.egl updates to "" + when(mSystemPropertiesMock.get(eq(PROPERTY_RO_GFX_ANGLE_SUPPORTED), any())) + .thenReturn("true"); mController.onDeveloperOptionsSwitchDisabled(); propertyChangeSignal1.wait(100); final String systemEGLDriver = SystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL); @@ -253,12 +239,16 @@ public class GraphicsDriverEnableAngleAsSystemDriverControllerJUnitTest { @Test public void onDeveloperOptionSwitchDisabled_PreferenceShouldNotBeChecked() { + when(mSystemPropertiesMock.get(eq(PROPERTY_RO_GFX_ANGLE_SUPPORTED), any())) + .thenReturn("true"); mController.onDeveloperOptionsSwitchDisabled(); assertThat(mPreference.isChecked()).isFalse(); } @Test public void onDeveloperOptionSwitchDisabled_PreferenceShouldDisabled() { + when(mSystemPropertiesMock.get(eq(PROPERTY_RO_GFX_ANGLE_SUPPORTED), any())) + .thenReturn("true"); mController.onDeveloperOptionsSwitchDisabled(); assertThat(mPreference.isEnabled()).isFalse(); } From ac347eddd962a2d69df5027fc90a46f694617c55 Mon Sep 17 00:00:00 2001 From: Viesturs Zarins Date: Tue, 16 May 2023 14:26:42 +0200 Subject: [PATCH 03/11] Fix missing Activity embedding allowlisting for Assistant. The normal settings fragments are already allowlisted via SubSettingActivity. ManageApplications fragment based activities are not compatible with that however. The ManageApplications fragment has several modes that are selected by the activity name, hence cannot use the SpringboardSettings activity to launch just the fragment. For more context, see go/conversational-care-activity-embedding-allowlisting BUG: 282921726 Change-Id: I65872b574ce6272fbd33ab7bd100504a6536f340 --- AndroidManifest.xml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 5904282dd97..98b440cbe99 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -864,6 +864,7 @@ @@ -1545,6 +1546,7 @@ @@ -1607,6 +1609,7 @@ @@ -1665,6 +1668,7 @@ This is for compatibility with old shortcuts. --> @@ -1761,6 +1767,7 @@ @@ -2105,6 +2112,7 @@ @@ -3483,6 +3491,7 @@ @@ -3662,6 +3671,7 @@ @@ -3676,6 +3686,7 @@ @@ -3941,6 +3952,7 @@ @@ -3978,6 +3990,7 @@ @@ -4011,6 +4024,7 @@ @@ -4041,6 +4055,7 @@ @@ -4095,6 +4110,7 @@ @@ -4126,6 +4142,7 @@ From 06c3ae5e82724345c253302f99010686807fcb35 Mon Sep 17 00:00:00 2001 From: tom hsu Date: Thu, 25 May 2023 22:29:55 +0800 Subject: [PATCH 04/11] [Settings] Avoid settings crash from NPE of TelephonyManager. Bug: 233783567 Test: atest passed Change-Id: Ie0ed8fa3b87b8e130396cf4d918295403ae43501 --- .../telephony/PreferredNetworkModePreferenceController.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/com/android/settings/network/telephony/PreferredNetworkModePreferenceController.java b/src/com/android/settings/network/telephony/PreferredNetworkModePreferenceController.java index feeed91910c..bdfeace1be6 100644 --- a/src/com/android/settings/network/telephony/PreferredNetworkModePreferenceController.java +++ b/src/com/android/settings/network/telephony/PreferredNetworkModePreferenceController.java @@ -21,6 +21,7 @@ import android.os.PersistableBundle; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.util.Log; import androidx.preference.ListPreference; import androidx.preference.Preference; @@ -34,6 +35,7 @@ import com.android.settings.network.telephony.TelephonyConstants.TelephonyManage */ public class PreferredNetworkModePreferenceController extends TelephonyBasePreferenceController implements ListPreference.OnPreferenceChangeListener { + private static final String TAG = "PrefNetworkModeCtrl"; private CarrierConfigCache mCarrierConfigCache; private TelephonyManager mTelephonyManager; @@ -99,6 +101,10 @@ public class PreferredNetworkModePreferenceController extends TelephonyBasePrefe } private int getPreferredNetworkMode() { + if (mTelephonyManager == null) { + Log.w(TAG, "TelephonyManager is null"); + return TelephonyManagerConstants.NETWORK_MODE_UNKNOWN; + } return MobileNetworkUtils.getNetworkTypeFromRaf( (int) mTelephonyManager.getAllowedNetworkTypesForReason( TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER)); From 2eadf68438cbfb57fa31be03aa3a33e2e502c455 Mon Sep 17 00:00:00 2001 From: Hao Dong Date: Mon, 15 May 2023 19:34:07 +0000 Subject: [PATCH 05/11] Fix Fingerprint enrollment UI when display size is largest. 1. Remove land/udfps_enroll_enrolling and use the default land layout instead. Swap header and content when necessary to avoid overlap. 2. Add UdfpsEnrollEnrollingView.java Test: manual test - 1. Set system display and font size largest 2. Launch fingerprint enrollment and check UI. Test: atest FingerprintEnrollEnrollingTest Bug: 269060514 Bug: 283169056 Change-Id: Ifbe6c92c4213979952f2f89a1cd595c9c4bff6ec --- res/layout-land/udfps_enroll_enrolling.xml | 101 -------- res/layout/udfps_enroll_enrolling.xml | 4 +- .../biometrics/BiometricEnrollBase.java | 17 +- .../FingerprintEnrollEnrolling.java | 197 +-------------- .../fingerprint/UdfpsEnrollEnrollingView.java | 237 ++++++++++++++++++ .../FingerprintEnrollEnrollingTest.java | 22 +- 6 files changed, 279 insertions(+), 299 deletions(-) delete mode 100644 res/layout-land/udfps_enroll_enrolling.xml create mode 100644 src/com/android/settings/biometrics/fingerprint/UdfpsEnrollEnrollingView.java diff --git a/res/layout-land/udfps_enroll_enrolling.xml b/res/layout-land/udfps_enroll_enrolling.xml deleted file mode 100644 index 743684fb02e..00000000000 --- a/res/layout-land/udfps_enroll_enrolling.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/layout/udfps_enroll_enrolling.xml b/res/layout/udfps_enroll_enrolling.xml index 05556ffe46c..366a87c4740 100644 --- a/res/layout/udfps_enroll_enrolling.xml +++ b/res/layout/udfps_enroll_enrolling.xml @@ -15,7 +15,7 @@ ~ limitations under the License. --> - - + diff --git a/src/com/android/settings/biometrics/BiometricEnrollBase.java b/src/com/android/settings/biometrics/BiometricEnrollBase.java index 2f852f08b9f..6e110799c46 100644 --- a/src/com/android/settings/biometrics/BiometricEnrollBase.java +++ b/src/com/android/settings/biometrics/BiometricEnrollBase.java @@ -133,6 +133,7 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity { protected long mChallenge; protected boolean mFromSettingsSummary; protected FooterBarMixin mFooterBarMixin; + protected boolean mShouldSetFooterBarBackground = true; @Nullable protected ScreenSizeFoldProvider mScreenSizeFoldProvider; @Nullable @@ -191,12 +192,14 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity { super.onPostCreate(savedInstanceState); initViews(); - @SuppressLint("VisibleForTests") - final LinearLayout buttonContainer = mFooterBarMixin != null - ? mFooterBarMixin.getButtonContainer() - : null; - if (buttonContainer != null) { - buttonContainer.setBackgroundColor(getBackgroundColor()); + if (mShouldSetFooterBarBackground) { + @SuppressLint("VisibleForTests") + final LinearLayout buttonContainer = mFooterBarMixin != null + ? mFooterBarMixin.getButtonContainer() + : null; + if (buttonContainer != null) { + buttonContainer.setBackgroundColor(getBackgroundColor()); + } } } @@ -331,7 +334,7 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity { } @ColorInt - private int getBackgroundColor() { + public int getBackgroundColor() { final ColorStateList stateList = Utils.getColorAttr(this, android.R.attr.windowBackground); return stateList != null ? stateList.getDefaultColor() : Color.TRANSPARENT; } diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java index f653942f3a9..400a92ee7a7 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java @@ -32,10 +32,8 @@ import android.content.Intent; import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; -import android.graphics.Point; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; -import android.graphics.Rect; import android.graphics.drawable.Animatable2; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.Drawable; @@ -48,22 +46,16 @@ import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.text.TextUtils; -import android.util.FeatureFlagUtils; import android.util.Log; -import android.view.DisplayInfo; import android.view.MotionEvent; import android.view.OrientationEventListener; import android.view.Surface; import android.view.View; -import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; @@ -79,25 +71,20 @@ import com.android.settings.biometrics.BiometricUtils; import com.android.settings.biometrics.BiometricsEnrollEnrolling; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settingslib.display.DisplayDensityUtils; -import com.android.settingslib.udfps.UdfpsOverlayParams; -import com.android.settingslib.udfps.UdfpsUtils; 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; -import com.google.android.setupdesign.GlifLayout; import com.google.android.setupdesign.template.DescriptionMixin; import com.google.android.setupdesign.template.HeaderMixin; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; -import java.util.Locale; /** * Activity which handles the actual enrolling for fingerprint. @@ -176,8 +163,6 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { @VisibleForTesting @Nullable UdfpsEnrollHelper mUdfpsEnrollHelper; - // TODO(b/260617060): Do not hard-code mScaleFactor, referring to AuthController. - private float mScaleFactor = 1.0f; private ObjectAnimator mProgressAnim; private TextView mErrorText; private Interpolator mFastOutSlowInInterpolator; @@ -206,7 +191,7 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { private boolean mHaveShownSfpsLeftEdgeLottie; private boolean mHaveShownSfpsRightEdgeLottie; private boolean mShouldShowLottie; - private UdfpsUtils mUdfpsUtils; + private ObjectAnimator mHelpAnimation; private OrientationEventListener mOrientationEventListener; @@ -251,88 +236,17 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { mAccessibilityManager = getSystemService(AccessibilityManager.class); mIsAccessibilityEnabled = mAccessibilityManager.isEnabled(); - mUdfpsUtils = new UdfpsUtils(); - final boolean isLayoutRtl = (TextUtils.getLayoutDirectionFromLocale( - Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL); listenOrientationEvent(); if (mCanAssumeUdfps) { - int rotation = getApplicationContext().getDisplay().getRotation(); - final GlifLayout layout = (GlifLayout) getLayoutInflater().inflate( - R.layout.udfps_enroll_enrolling, null, false); - final UdfpsEnrollView udfpsEnrollView = layout.findViewById(R.id.udfps_animation_view); - updateUdfpsEnrollView(udfpsEnrollView, props.get(0)); - switch (rotation) { - case Surface.ROTATION_90: - final View sudContent = layout.findViewById(R.id.sud_layout_content); - if (sudContent != null) { - sudContent.setPadding(sudContent.getPaddingLeft(), 0, - sudContent.getPaddingRight(), sudContent.getPaddingBottom()); - } + final UdfpsEnrollEnrollingView layout = + (UdfpsEnrollEnrollingView) getLayoutInflater().inflate( + R.layout.udfps_enroll_enrolling, null, false); + setUdfpsEnrollHelper(); + layout.initView(props.get(0), mUdfpsEnrollHelper, mAccessibilityManager); - final LinearLayout layoutContainer = layout.findViewById( - R.id.layout_container); - final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.MATCH_PARENT); - - lp.setMarginEnd((int) getResources().getDimension( - R.dimen.rotation_90_enroll_margin_end)); - layoutContainer.setPaddingRelative((int) getResources().getDimension( - R.dimen.rotation_90_enroll_padding_start), 0, isLayoutRtl - ? 0 : (int) getResources().getDimension( - R.dimen.rotation_90_enroll_padding_end), 0); - layoutContainer.setLayoutParams(lp); - - setOnHoverListener(true, layout, udfpsEnrollView); - setContentView(layout, lp); - break; - - case Surface.ROTATION_0: - case Surface.ROTATION_180: - // In the portrait mode, layout_container's height is 0, so it's - // always shown at the bottom of the screen. - final FrameLayout portraitLayoutContainer = layout.findViewById( - R.id.layout_container); - - // In the portrait mode, the title and lottie animation view may - // overlap when title needs three lines, so adding some paddings - // between them, and adjusting the fp progress view here accordingly. - final int layoutLottieAnimationPadding = (int) getResources() - .getDimension(R.dimen.udfps_lottie_padding_top); - portraitLayoutContainer.setPadding(0, - layoutLottieAnimationPadding, 0, 0); - final ImageView progressView = udfpsEnrollView.findViewById( - R.id.udfps_enroll_animation_fp_progress_view); - progressView.setPadding(0, -(layoutLottieAnimationPadding), - 0, layoutLottieAnimationPadding); - final ImageView fingerprintView = udfpsEnrollView.findViewById( - R.id.udfps_enroll_animation_fp_view); - fingerprintView.setPadding(0, -layoutLottieAnimationPadding, - 0, layoutLottieAnimationPadding); - - // TODO(b/260970216) Instead of hiding the description text view, we should - // make the header view scrollable if the text is too long. - // If description text view has overlap with udfps progress view, hide it. - View view = layout.getDescriptionTextView(); - layout.getViewTreeObserver().addOnDrawListener(() -> { - if (view.getVisibility() == View.VISIBLE - && hasOverlap(view, udfpsEnrollView)) { - view.setVisibility(View.GONE); - } - }); - - setOnHoverListener(false, layout, udfpsEnrollView); - setContentView(layout); - break; - - case Surface.ROTATION_270: - default: - setOnHoverListener(true, layout, udfpsEnrollView); - setContentView(layout); - break; - } + setContentView(layout); setDescriptionText(R.string.security_settings_udfps_enroll_start_message); } else if (mCanAssumeSfps) { setContentView(R.layout.sfps_enroll_enrolling); @@ -372,22 +286,11 @@ 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; - } + // If it's udfps, set the background color only for secondary button if necessary. + if (mCanAssumeUdfps) { + mShouldSetFooterBarBackground = false; + ((UdfpsEnrollEnrollingView) getLayout()).setSecondaryButtonBackground( + getBackgroundColor()); } final LayerDrawable fingerprintDrawable = mProgressBar != null @@ -1230,30 +1133,7 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { } } - private UdfpsEnrollView updateUdfpsEnrollView(UdfpsEnrollView udfpsEnrollView, - FingerprintSensorPropertiesInternal udfpsProps) { - DisplayInfo displayInfo = new DisplayInfo(); - getDisplay().getDisplayInfo(displayInfo); - mScaleFactor = mUdfpsUtils.getScaleFactor(displayInfo); - Rect udfpsBounds = udfpsProps.getLocation().getRect(); - udfpsBounds.scale(mScaleFactor); - - final Rect overlayBounds = new Rect( - 0, /* left */ - displayInfo.getNaturalHeight() / 2, /* top */ - displayInfo.getNaturalWidth(), /* right */ - displayInfo.getNaturalHeight() /* botom */); - - UdfpsOverlayParams params = new UdfpsOverlayParams( - udfpsBounds, - overlayBounds, - displayInfo.getNaturalWidth(), - displayInfo.getNaturalHeight(), - mScaleFactor, - displayInfo.rotation); - - udfpsEnrollView.setOverlayParams(params); - + private void setUdfpsEnrollHelper() { mUdfpsEnrollHelper = (UdfpsEnrollHelper) getSupportFragmentManager().findFragmentByTag( FingerprintEnrollEnrolling.TAG_UDFPS_HELPER); if (mUdfpsEnrollHelper == null) { @@ -1263,57 +1143,6 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { .add(mUdfpsEnrollHelper, FingerprintEnrollEnrolling.TAG_UDFPS_HELPER) .commitAllowingStateLoss(); } - udfpsEnrollView.setEnrollHelper(mUdfpsEnrollHelper); - - return udfpsEnrollView; - } - - private void setOnHoverListener(boolean isLandscape, GlifLayout enrollLayout, - UdfpsEnrollView udfpsEnrollView) { - if (!mIsAccessibilityEnabled) return; - - final Context context = getApplicationContext(); - final View.OnHoverListener onHoverListener = (v, event) -> { - // Map the touch to portrait mode if the device is in - // landscape mode. - final Point scaledTouch = - mUdfpsUtils.getTouchInNativeCoordinates(event.getPointerId(0), - event, udfpsEnrollView.getOverlayParams()); - - if (mUdfpsUtils.isWithinSensorArea(event.getPointerId(0), event, - udfpsEnrollView.getOverlayParams())) { - return false; - } - - final String theStr = mUdfpsUtils.onTouchOutsideOfSensorArea( - mAccessibilityManager.isTouchExplorationEnabled(), context, - scaledTouch.x, scaledTouch.y, udfpsEnrollView.getOverlayParams()); - if (theStr != null) { - v.announceForAccessibility(theStr); - } - return false; - }; - - enrollLayout.findManagedViewById(isLandscape ? R.id.sud_landscape_content_area - : R.id.sud_layout_content).setOnHoverListener(onHoverListener); - } - - - @VisibleForTesting boolean hasOverlap(View view1, View view2) { - int[] firstPosition = new int[2]; - int[] secondPosition = new int[2]; - - view1.getLocationOnScreen(firstPosition); - view2.getLocationOnScreen(secondPosition); - - // Rect constructor parameters: left, top, right, bottom - Rect rectView1 = new Rect(firstPosition[0], firstPosition[1], - firstPosition[0] + view1.getMeasuredWidth(), - firstPosition[1] + view1.getMeasuredHeight()); - Rect rectView2 = new Rect(secondPosition[0], secondPosition[1], - secondPosition[0] + view2.getMeasuredWidth(), - secondPosition[1] + view2.getMeasuredHeight()); - return rectView1.intersect(rectView2); } public static class IconTouchDialog extends InstrumentedDialogFragment { diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollEnrollingView.java b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollEnrollingView.java new file mode 100644 index 00000000000..9225c64498f --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollEnrollingView.java @@ -0,0 +1,237 @@ +/* + * 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.biometrics.fingerprint; + +import android.content.Context; +import android.graphics.Point; +import android.graphics.Rect; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.DisplayInfo; +import android.view.Gravity; +import android.view.Surface; +import android.view.View; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityManager; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import androidx.annotation.ColorInt; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.R; +import com.android.settingslib.udfps.UdfpsOverlayParams; +import com.android.settingslib.udfps.UdfpsUtils; + +import com.google.android.setupcompat.template.FooterBarMixin; +import com.google.android.setupdesign.GlifLayout; +import com.google.android.setupdesign.view.BottomScrollView; + +import java.util.Locale; + +/** + * View for udfps enrolling. + */ +public class UdfpsEnrollEnrollingView extends GlifLayout { + private final UdfpsUtils mUdfpsUtils; + private final Context mContext; + // We don't need to listen to onConfigurationChanged() for mRotation here because + // FingerprintEnrollEnrolling is always recreated once the configuration is changed. + private final int mRotation; + private final boolean mIsLandscape; + private final boolean mShouldUseReverseLandscape; + private UdfpsEnrollView mUdfpsEnrollView; + private View mHeaderView; + private AccessibilityManager mAccessibilityManager; + + + public UdfpsEnrollEnrollingView(Context context, AttributeSet attrs) { + super(context, attrs); + mContext = context; + mRotation = mContext.getDisplay().getRotation(); + mIsLandscape = mRotation == Surface.ROTATION_90 || mRotation == Surface.ROTATION_270; + final boolean isLayoutRtl = (TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) + == View.LAYOUT_DIRECTION_RTL); + mShouldUseReverseLandscape = (mRotation == Surface.ROTATION_90 && isLayoutRtl) + || (mRotation == Surface.ROTATION_270 && !isLayoutRtl); + + mUdfpsUtils = new UdfpsUtils(); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mHeaderView = findViewById(R.id.sud_landscape_header_area); + mUdfpsEnrollView = findViewById(R.id.udfps_animation_view); + } + + void initView(FingerprintSensorPropertiesInternal udfpsProps, + UdfpsEnrollHelper udfpsEnrollHelper, + AccessibilityManager accessibilityManager) { + mAccessibilityManager = accessibilityManager; + initUdfpsEnrollView(mUdfpsEnrollView, udfpsProps, udfpsEnrollHelper); + + if (!mIsLandscape) { + adjustPortraitPaddings(); + } else if (mShouldUseReverseLandscape) { + swapHeaderAndContent(); + } + setOnHoverListener(); + } + + void setSecondaryButtonBackground(@ColorInt int color) { + // Set the button background only when the button is not under udfps overlay to avoid UI + // overlap. + if (!mIsLandscape || mShouldUseReverseLandscape) { + return; + } + final Button secondaryButtonView = + getMixin(FooterBarMixin.class).getSecondaryButtonView(); + secondaryButtonView.setBackgroundColor(color); + if (mRotation == Surface.ROTATION_90) { + secondaryButtonView.setGravity(Gravity.START); + } else { + secondaryButtonView.setGravity(Gravity.END); + } + mHeaderView.post(() -> { + secondaryButtonView.setLayoutParams( + new LinearLayout.LayoutParams(mHeaderView.getMeasuredWidth(), + ViewGroup.LayoutParams.WRAP_CONTENT)); + }); + } + + private void initUdfpsEnrollView(UdfpsEnrollView udfpsEnrollView, + FingerprintSensorPropertiesInternal udfpsProps, + UdfpsEnrollHelper udfpsEnrollHelper) { + DisplayInfo displayInfo = new DisplayInfo(); + mContext.getDisplay().getDisplayInfo(displayInfo); + + final float scaleFactor = mUdfpsUtils.getScaleFactor(displayInfo); + Rect udfpsBounds = udfpsProps.getLocation().getRect(); + udfpsBounds.scale(scaleFactor); + + final Rect overlayBounds = new Rect( + 0, /* left */ + displayInfo.getNaturalHeight() / 2, /* top */ + displayInfo.getNaturalWidth(), /* right */ + displayInfo.getNaturalHeight() /* botom */); + + UdfpsOverlayParams params = new UdfpsOverlayParams( + udfpsBounds, + overlayBounds, + displayInfo.getNaturalWidth(), + displayInfo.getNaturalHeight(), + scaleFactor, + displayInfo.rotation); + + udfpsEnrollView.setOverlayParams(params); + udfpsEnrollView.setEnrollHelper(udfpsEnrollHelper); + } + + private void adjustPortraitPaddings() { + // In the portrait mode, layout_container's height is 0, so it's + // always shown at the bottom of the screen. + final FrameLayout portraitLayoutContainer = findViewById(R.id.layout_container); + + // In the portrait mode, the title and lottie animation view may + // overlap when title needs three lines, so adding some paddings + // between them, and adjusting the fp progress view here accordingly. + final int layoutLottieAnimationPadding = (int) getResources() + .getDimension(R.dimen.udfps_lottie_padding_top); + portraitLayoutContainer.setPadding(0, + layoutLottieAnimationPadding, 0, 0); + final ImageView progressView = mUdfpsEnrollView.findViewById( + R.id.udfps_enroll_animation_fp_progress_view); + progressView.setPadding(0, -(layoutLottieAnimationPadding), + 0, layoutLottieAnimationPadding); + final ImageView fingerprintView = mUdfpsEnrollView.findViewById( + R.id.udfps_enroll_animation_fp_view); + fingerprintView.setPadding(0, -layoutLottieAnimationPadding, + 0, layoutLottieAnimationPadding); + + // TODO(b/260970216) Instead of hiding the description text view, we should + // make the header view scrollable if the text is too long. + // If description text view has overlap with udfps progress view, hide it. + final View descView = getDescriptionTextView(); + getViewTreeObserver().addOnDrawListener(() -> { + if (descView.getVisibility() == View.VISIBLE + && hasOverlap(descView, mUdfpsEnrollView)) { + descView.setVisibility(View.GONE); + } + }); + } + + private void setOnHoverListener() { + if (!mAccessibilityManager.isEnabled()) return; + + final View.OnHoverListener onHoverListener = (v, event) -> { + // Map the touch to portrait mode if the device is in + // landscape mode. + final Point scaledTouch = + mUdfpsUtils.getTouchInNativeCoordinates(event.getPointerId(0), + event, mUdfpsEnrollView.getOverlayParams()); + + if (mUdfpsUtils.isWithinSensorArea(event.getPointerId(0), event, + mUdfpsEnrollView.getOverlayParams())) { + return false; + } + + final String theStr = mUdfpsUtils.onTouchOutsideOfSensorArea( + mAccessibilityManager.isTouchExplorationEnabled(), mContext, + scaledTouch.x, scaledTouch.y, mUdfpsEnrollView.getOverlayParams()); + if (theStr != null) { + v.announceForAccessibility(theStr); + } + return false; + }; + + findManagedViewById(mIsLandscape ? R.id.sud_landscape_content_area + : R.id.sud_layout_content).setOnHoverListener(onHoverListener); + } + + private void swapHeaderAndContent() { + // Reverse header and body + ViewGroup parentView = (ViewGroup) mHeaderView.getParent(); + parentView.removeView(mHeaderView); + parentView.addView(mHeaderView); + + // Hide scroll indicators + BottomScrollView headerScrollView = mHeaderView.findViewById(R.id.sud_header_scroll_view); + headerScrollView.setScrollIndicators(0); + } + + @VisibleForTesting + boolean hasOverlap(View view1, View view2) { + int[] firstPosition = new int[2]; + int[] secondPosition = new int[2]; + + view1.getLocationOnScreen(firstPosition); + view2.getLocationOnScreen(secondPosition); + + // Rect constructor parameters: left, top, right, bottom + Rect rectView1 = new Rect(firstPosition[0], firstPosition[1], + firstPosition[0] + view1.getMeasuredWidth(), + firstPosition[1] + view1.getMeasuredHeight()); + Rect rectView2 = new Rect(secondPosition[0], secondPosition[1], + secondPosition[0] + view2.getMeasuredWidth(), + secondPosition[1] + view2.getMeasuredHeight()); + return rectView1.intersect(rectView2); + } +} diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java index 959c6426894..8c84128913f 100644 --- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java +++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java @@ -56,6 +56,7 @@ import android.os.Vibrator; import android.view.Display; import android.view.Surface; import android.view.View; +import android.view.accessibility.AccessibilityManager; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; @@ -314,11 +315,17 @@ public class FingerprintEnrollEnrollingTest { @Test public void fingerprintUdfpsOverlayEnrollment_descriptionViewGoneWithOverlap() { initializeActivityWithoutCreate(TYPE_UDFPS_OPTICAL); - doReturn(true).when(mActivity).hasOverlap(any(), any()); when(mMockDisplay.getRotation()).thenReturn(Surface.ROTATION_0); createActivity(); - final GlifLayout defaultLayout = spy(mActivity.findViewById(R.id.setup_wizard_layout)); + final UdfpsEnrollEnrollingView defaultLayout = spy( + mActivity.findViewById(R.id.setup_wizard_layout)); + doReturn(true).when(defaultLayout).hasOverlap(any(), any()); + + // Somehow spy doesn't work, and we need to call initView manually. + defaultLayout.initView(mFingerprintManager.getSensorPropertiesInternal().get(0), + mActivity.mUdfpsEnrollHelper, + mActivity.getSystemService(AccessibilityManager.class)); final TextView descriptionTextView = defaultLayout.getDescriptionTextView(); defaultLayout.getViewTreeObserver().dispatchOnDraw(); @@ -328,11 +335,17 @@ public class FingerprintEnrollEnrollingTest { @Test public void fingerprintUdfpsOverlayEnrollment_descriptionViewVisibleWithoutOverlap() { initializeActivityWithoutCreate(TYPE_UDFPS_OPTICAL); - doReturn(false).when(mActivity).hasOverlap(any(), any()); when(mMockDisplay.getRotation()).thenReturn(Surface.ROTATION_0); createActivity(); - final GlifLayout defaultLayout = spy(mActivity.findViewById(R.id.setup_wizard_layout)); + final UdfpsEnrollEnrollingView defaultLayout = spy( + mActivity.findViewById(R.id.setup_wizard_layout)); + doReturn(false).when(defaultLayout).hasOverlap(any(), any()); + + // Somehow spy doesn't work, and we need to call initView manually. + defaultLayout.initView(mFingerprintManager.getSensorPropertiesInternal().get(0), + mActivity.mUdfpsEnrollHelper, + mActivity.getSystemService(AccessibilityManager.class)); final TextView descriptionTextView = defaultLayout.getDescriptionTextView(); defaultLayout.getViewTreeObserver().dispatchOnDraw(); @@ -578,7 +591,6 @@ public class FingerprintEnrollEnrollingTest { mContext = spy(RuntimeEnvironment.application); mActivity = spy(FingerprintEnrollEnrolling.class); - when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props); when(mContext.getDisplay()).thenReturn(mMockDisplay); when(mMockDisplay.getRotation()).thenReturn(Surface.ROTATION_0); From f7d8f0481ddf05ecbbe30e1c2dca8bdd0b09944d Mon Sep 17 00:00:00 2001 From: Weng Su Date: Fri, 26 May 2023 12:26:31 +0800 Subject: [PATCH 06/11] Fixed unable to enable hotspot with Security None - Due to the design adjustment, the default value of SoftApConfiguration is not Security None, so the update of Security None cannot be omitted. Bug: 283762826 Test: Manual test atest -c WifiTetherSettingsTest Change-Id: I07d18b8f73ab8190152332c01814a4bd72cf28d6 --- .../wifi/tether/WifiTetherSettings.java | 23 +++++++++-------- .../wifi/tether/WifiTetherSettingsTest.java | 25 +++++++++++++++++++ 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/com/android/settings/wifi/tether/WifiTetherSettings.java b/src/com/android/settings/wifi/tether/WifiTetherSettings.java index 4ce59b9b53f..88d601a082b 100644 --- a/src/com/android/settings/wifi/tether/WifiTetherSettings.java +++ b/src/com/android/settings/wifi/tether/WifiTetherSettings.java @@ -84,8 +84,10 @@ public class WifiTetherSettings extends RestrictedDashboardFragment WifiTetherSSIDPreferenceController mSSIDPreferenceController; @VisibleForTesting WifiTetherPasswordPreferenceController mPasswordPreferenceController; - private WifiTetherSecurityPreferenceController mSecurityPreferenceController; - private WifiTetherMaximizeCompatibilityPreferenceController mMaxCompatibilityPrefController; + @VisibleForTesting + WifiTetherSecurityPreferenceController mSecurityPreferenceController; + @VisibleForTesting + WifiTetherMaximizeCompatibilityPreferenceController mMaxCompatibilityPrefController; @VisibleForTesting WifiTetherAutoOffPreferenceController mWifiTetherAutoOffPreferenceController; @@ -276,15 +278,16 @@ public class WifiTetherSettings extends RestrictedDashboardFragment SoftApConfiguration buildNewConfig() { SoftApConfiguration currentConfig = mWifiTetherViewModel.getSoftApConfiguration(); SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(currentConfig); - int securityType = (mWifiTetherViewModel.isSpeedFeatureAvailable()) - ? currentConfig.getSecurityType() - : mSecurityPreferenceController.getSecurityType(); configBuilder.setSsid(mSSIDPreferenceController.getSSID()); - if (securityType != SoftApConfiguration.SECURITY_TYPE_OPEN) { - configBuilder.setPassphrase( - mPasswordPreferenceController.getPasswordValidated(securityType), - securityType); - } + int securityType = + mWifiTetherViewModel.isSpeedFeatureAvailable() + ? currentConfig.getSecurityType() + : mSecurityPreferenceController.getSecurityType(); + String passphrase = + securityType == SoftApConfiguration.SECURITY_TYPE_OPEN + ? null + : mPasswordPreferenceController.getPasswordValidated(securityType); + configBuilder.setPassphrase(passphrase, securityType); if (!mWifiTetherViewModel.isSpeedFeatureAvailable()) { mMaxCompatibilityPrefController.setupMaximizeCompatibility(configBuilder); } diff --git a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java index fe663abbc28..5005f4c9d5a 100644 --- a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java +++ b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java @@ -16,6 +16,7 @@ package com.android.settings.wifi.tether; +import static android.net.wifi.SoftApConfiguration.SECURITY_TYPE_OPEN; import static android.net.wifi.SoftApConfiguration.SECURITY_TYPE_WPA3_SAE; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; @@ -125,9 +126,13 @@ public class WifiTetherSettingsTest { @Mock private WifiTetherSSIDPreferenceController mSSIDPreferenceController; @Mock + private WifiTetherSecurityPreferenceController mSecurityPreferenceController; + @Mock private WifiTetherPasswordPreferenceController mPasswordPreferenceController; @Mock private WifiTetherAutoOffPreferenceController mWifiTetherAutoOffPreferenceController; + @Mock + private WifiTetherMaximizeCompatibilityPreferenceController mMaxCompatibilityPrefController; private WifiTetherSettings mSettings; @@ -156,10 +161,13 @@ public class WifiTetherSettingsTest { mSettings.mMainSwitchBar = mMainSwitchBar; mSettings.mSSIDPreferenceController = mSSIDPreferenceController; when(mSSIDPreferenceController.getSSID()).thenReturn(SSID); + mSettings.mSecurityPreferenceController = mSecurityPreferenceController; + when(mSecurityPreferenceController.getSecurityType()).thenReturn(SECURITY_TYPE_WPA3_SAE); mSettings.mPasswordPreferenceController = mPasswordPreferenceController; when(mPasswordPreferenceController.getPasswordValidated(anyInt())).thenReturn(PASSWORD); mSettings.mWifiTetherAutoOffPreferenceController = mWifiTetherAutoOffPreferenceController; when(mWifiTetherAutoOffPreferenceController.isEnabled()).thenReturn(true); + mSettings.mMaxCompatibilityPrefController = mMaxCompatibilityPrefController; mSettings.mWifiTetherViewModel = mWifiTetherViewModel; when(mSettings.findPreference(KEY_WIFI_HOTSPOT_SECURITY)).thenReturn(mWifiHotspotSecurity); when(mSettings.findPreference(KEY_WIFI_HOTSPOT_SPEED)).thenReturn(mWifiHotspotSpeed); @@ -359,6 +367,23 @@ public class WifiTetherSettingsTest { assertThat(newConfig.getBand()).isEqualTo(currentConfig.getBand()); } + @Test + public void buildNewConfig_securityTypeChangeToOpen_setSecurityTypeCorrectly() { + SoftApConfiguration currentConfig = new SoftApConfiguration.Builder() + .setPassphrase(PASSWORD, SECURITY_TYPE_WPA3_SAE) + .setBand(BAND_2GHZ_5GHZ_6GHZ) + .build(); + when(mWifiTetherViewModel.getSoftApConfiguration()).thenReturn(currentConfig); + when(mWifiTetherViewModel.isSpeedFeatureAvailable()).thenReturn(false); + doNothing().when(mMaxCompatibilityPrefController) + .setupMaximizeCompatibility(any(SoftApConfiguration.Builder.class)); + + when(mSecurityPreferenceController.getSecurityType()).thenReturn(SECURITY_TYPE_OPEN); + SoftApConfiguration newConfig = mSettings.buildNewConfig(); + + assertThat(newConfig.getSecurityType()).isEqualTo(SECURITY_TYPE_OPEN); + } + @Test public void onRestartingChanged_restartingFalse_setLoadingFalse() { doNothing().when(mSettings).setLoading(anyBoolean(), anyBoolean()); From 3e35cff7ba977cd9bebb36f400cb2219b9fff16d Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Fri, 26 May 2023 11:38:38 +0800 Subject: [PATCH 07/11] Move unrestricted data summary to DataSaverController DataSaverController currently used in 2 pages, - Network & internet > Data Saver - Apps > Special app access This helps unify the logic, and instead of showing on "Special app access", this summary shows on "Unrestricted data" is make more sense. Bug: 280280596 Test: Manually on above pages Test: Unit test Change-Id: Ia151ed8179a250f8f20cc5041f9383fffebdab10 --- res/values/strings.xml | 6 - res/xml/apps.xml | 3 +- .../applications/AppDashboardFragment.java | 1 - .../SpecialAppAccessPreferenceController.java | 154 ------------------ .../specialaccess/DataSaverController.java | 36 ---- .../specialaccess/DataSaverController.kt | 88 ++++++++++ .../specialaccess/SpecialAccessSettings.java | 10 ++ .../settings/datausage/DataSaverSummary.kt | 60 ++----- ...cialAppAccessPreferenceControllerTest.java | 125 -------------- .../DataSaverControllerTest.java | 74 --------- .../specialaccess/DataSaverControllerTest.kt} | 31 +++- 11 files changed, 139 insertions(+), 449 deletions(-) delete mode 100644 src/com/android/settings/applications/SpecialAppAccessPreferenceController.java delete mode 100644 src/com/android/settings/applications/specialaccess/DataSaverController.java create mode 100644 src/com/android/settings/applications/specialaccess/DataSaverController.kt delete mode 100644 tests/robotests/src/com/android/settings/applications/SpecialAppAccessPreferenceControllerTest.java delete mode 100644 tests/robotests/src/com/android/settings/applications/specialaccess/DataSaverControllerTest.java rename tests/spa_unit/src/com/android/settings/{datausage/DataSaverSummaryTest.kt => applications/specialaccess/DataSaverControllerTest.kt} (76%) diff --git a/res/values/strings.xml b/res/values/strings.xml index 349a4ab5768..bdf80a30866 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -9702,12 +9702,6 @@ Special app access - - - 1 app can use unrestricted data - %d apps can use unrestricted data - - See more diff --git a/res/xml/apps.xml b/res/xml/apps.xml index ae51bae9063..03212c914fe 100644 --- a/res/xml/apps.xml +++ b/res/xml/apps.xml @@ -105,7 +105,6 @@ android:key="special_access" android:fragment="com.android.settings.applications.specialaccess.SpecialAccessSettings" android:title="@string/special_access" - android:order="20" - settings:controller="com.android.settings.applications.SpecialAppAccessPreferenceController"/> + android:order="20"/> diff --git a/src/com/android/settings/applications/AppDashboardFragment.java b/src/com/android/settings/applications/AppDashboardFragment.java index 7e203b00e3e..11f8405ae56 100644 --- a/src/com/android/settings/applications/AppDashboardFragment.java +++ b/src/com/android/settings/applications/AppDashboardFragment.java @@ -66,7 +66,6 @@ public class AppDashboardFragment extends DashboardFragment { @Override public void onAttach(Context context) { super.onAttach(context); - use(SpecialAppAccessPreferenceController.class).setSession(getSettingsLifecycle()); mAppsPreferenceController = use(AppsPreferenceController.class); mAppsPreferenceController.setFragment(this /* fragment */); getSettingsLifecycle().addObserver(mAppsPreferenceController); diff --git a/src/com/android/settings/applications/SpecialAppAccessPreferenceController.java b/src/com/android/settings/applications/SpecialAppAccessPreferenceController.java deleted file mode 100644 index 42f5930ed62..00000000000 --- a/src/com/android/settings/applications/SpecialAppAccessPreferenceController.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2017 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.applications; - -import android.app.Application; -import android.content.Context; - -import androidx.annotation.VisibleForTesting; -import androidx.preference.Preference; -import androidx.preference.PreferenceScreen; - -import com.android.settings.R; -import com.android.settings.core.BasePreferenceController; -import com.android.settings.datausage.AppStateDataUsageBridge; -import com.android.settings.datausage.AppStateDataUsageBridge.DataUsageState; -import com.android.settings.datausage.DataSaverBackend; -import com.android.settingslib.applications.ApplicationsState; -import com.android.settingslib.core.lifecycle.Lifecycle; -import com.android.settingslib.core.lifecycle.LifecycleObserver; -import com.android.settingslib.core.lifecycle.events.OnDestroy; -import com.android.settingslib.core.lifecycle.events.OnStart; -import com.android.settingslib.core.lifecycle.events.OnStop; - -import java.util.ArrayList; - -public class SpecialAppAccessPreferenceController extends BasePreferenceController implements - AppStateBaseBridge.Callback, ApplicationsState.Callbacks, LifecycleObserver, OnStart, - OnStop, OnDestroy { - - @VisibleForTesting - ApplicationsState.Session mSession; - - private final ApplicationsState mApplicationsState; - private final AppStateDataUsageBridge mDataUsageBridge; - private final DataSaverBackend mDataSaverBackend; - - private Preference mPreference; - private boolean mExtraLoaded; - - - public SpecialAppAccessPreferenceController(Context context, String key) { - super(context, key); - mApplicationsState = ApplicationsState.getInstance( - (Application) context.getApplicationContext()); - mDataSaverBackend = new DataSaverBackend(context); - mDataUsageBridge = new AppStateDataUsageBridge(mApplicationsState, this, mDataSaverBackend); - } - - public void setSession(Lifecycle lifecycle) { - mSession = mApplicationsState.newSession(this, lifecycle); - } - - @Override - public int getAvailabilityStatus() { - return AVAILABLE; - } - - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - mPreference = screen.findPreference(getPreferenceKey()); - } - - @Override - public void onStart() { - mDataUsageBridge.resume(true /* forceLoadAllApps */); - } - - @Override - public void onStop() { - mDataUsageBridge.pause(); - } - - @Override - public void onDestroy() { - mDataUsageBridge.release(); - } - - @Override - public void updateState(Preference preference) { - updateSummary(); - } - - @Override - public void onExtraInfoUpdated() { - mExtraLoaded = true; - updateSummary(); - } - - private void updateSummary() { - if (!mExtraLoaded || mPreference == null) { - return; - } - - final ArrayList allApps = mSession.getAllApps(); - int count = 0; - for (ApplicationsState.AppEntry entry : allApps) { - if (!ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER.filterApp(entry)) { - continue; - } - if (entry.extraInfo instanceof DataUsageState - && ((DataUsageState) entry.extraInfo).isDataSaverAllowlisted) { - count++; - } - } - mPreference.setSummary(mContext.getResources().getQuantityString( - R.plurals.special_access_summary, count, count)); - } - - @Override - public void onRunningStateChanged(boolean running) { - } - - @Override - public void onPackageListChanged() { - } - - @Override - public void onRebuildComplete(ArrayList apps) { - } - - @Override - public void onPackageIconChanged() { - } - - @Override - public void onPackageSizeChanged(String packageName) { - } - - @Override - public void onAllSizesComputed() { - } - - @Override - public void onLauncherInfoChanged() { - // when the value of the AppEntry.hasLauncherEntry was changed. - updateSummary(); - } - - @Override - public void onLoadEntriesCompleted() { - } -} diff --git a/src/com/android/settings/applications/specialaccess/DataSaverController.java b/src/com/android/settings/applications/specialaccess/DataSaverController.java deleted file mode 100644 index d1fd202aae3..00000000000 --- a/src/com/android/settings/applications/specialaccess/DataSaverController.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2017 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.applications.specialaccess; - -import android.content.Context; - -import com.android.settings.R; -import com.android.settings.core.BasePreferenceController; - -public class DataSaverController extends BasePreferenceController { - - public DataSaverController(Context context, String key) { - super(context, key); - } - - @AvailabilityStatus - public int getAvailabilityStatus() { - return mContext.getResources().getBoolean(R.bool.config_show_data_saver) - ? AVAILABLE : UNSUPPORTED_ON_DEVICE; - } -} diff --git a/src/com/android/settings/applications/specialaccess/DataSaverController.kt b/src/com/android/settings/applications/specialaccess/DataSaverController.kt new file mode 100644 index 00000000000..3a2fdb002b1 --- /dev/null +++ b/src/com/android/settings/applications/specialaccess/DataSaverController.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2017 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.applications.specialaccess + +import android.content.Context +import android.net.NetworkPolicyManager +import android.os.UserHandle +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.preference.Preference +import androidx.preference.PreferenceScreen +import com.android.settings.R +import com.android.settings.core.BasePreferenceController +import com.android.settingslib.spa.framework.util.formatString +import com.android.settingslib.spaprivileged.model.app.AppListRepository +import com.android.settingslib.spaprivileged.model.app.AppListRepositoryImpl +import com.google.common.annotations.VisibleForTesting +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class DataSaverController(context: Context, key: String) : BasePreferenceController(context, key) { + + private lateinit var preference: Preference + + @AvailabilityStatus + override fun getAvailabilityStatus(): Int = when { + mContext.resources.getBoolean(R.bool.config_show_data_saver) -> AVAILABLE + else -> UNSUPPORTED_ON_DEVICE + } + + override fun displayPreference(screen: PreferenceScreen) { + super.displayPreference(screen) + preference = screen.findPreference(preferenceKey)!! + } + + fun init(viewLifecycleOwner: LifecycleOwner) { + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + preference.summary = getUnrestrictedSummary(mContext) + } + } + } + + companion object { + @VisibleForTesting + suspend fun getUnrestrictedSummary( + context: Context, + appListRepository: AppListRepository = + AppListRepositoryImpl(context.applicationContext), + ) = context.formatString( + R.string.data_saver_unrestricted_summary, + "count" to getAllowCount(context.applicationContext, appListRepository), + ) + + private suspend fun getAllowCount(context: Context, appListRepository: AppListRepository) = + withContext(Dispatchers.IO) { + coroutineScope { + val appsDeferred = async { + appListRepository.loadAndFilterApps( + userId = UserHandle.myUserId(), + isSystemApp = false, + ) + } + val uidsAllowed = NetworkPolicyManager.from(context) + .getUidsWithPolicy(NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND) + appsDeferred.await().count { app -> app.uid in uidsAllowed } + } + } + } +} \ No newline at end of file diff --git a/src/com/android/settings/applications/specialaccess/SpecialAccessSettings.java b/src/com/android/settings/applications/specialaccess/SpecialAccessSettings.java index 2cbc30422fc..9f4c8958cf4 100644 --- a/src/com/android/settings/applications/specialaccess/SpecialAccessSettings.java +++ b/src/com/android/settings/applications/specialaccess/SpecialAccessSettings.java @@ -21,6 +21,10 @@ import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGE_DE import android.app.settings.SettingsEnums; import android.os.Bundle; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.settings.R; import com.android.settings.dashboard.DashboardFragment; @@ -46,6 +50,12 @@ public class SpecialAccessSettings extends DashboardFragment { MANAGE_DEVICE_ADMIN_APPS, R.string.manage_device_admin); } + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + use(DataSaverController.class).init(getViewLifecycleOwner()); + } + @Override protected int getPreferenceScreenResId() { return R.xml.special_access; diff --git a/src/com/android/settings/datausage/DataSaverSummary.kt b/src/com/android/settings/datausage/DataSaverSummary.kt index 7f38900bbd2..13fbbfa3069 100644 --- a/src/com/android/settings/datausage/DataSaverSummary.kt +++ b/src/com/android/settings/datausage/DataSaverSummary.kt @@ -17,34 +17,22 @@ package com.android.settings.datausage import android.app.settings.SettingsEnums import android.content.Context -import android.net.NetworkPolicyManager import android.os.Bundle -import android.os.UserHandle import android.telephony.SubscriptionManager +import android.view.View import android.widget.Switch -import androidx.annotation.VisibleForTesting -import androidx.lifecycle.lifecycleScope -import androidx.preference.Preference import com.android.settings.R import com.android.settings.SettingsActivity -import com.android.settings.SettingsPreferenceFragment +import com.android.settings.applications.specialaccess.DataSaverController +import com.android.settings.dashboard.DashboardFragment import com.android.settings.search.BaseSearchIndexProvider import com.android.settings.widget.SettingsMainSwitchBar import com.android.settingslib.search.SearchIndexable -import com.android.settingslib.spa.framework.util.formatString -import com.android.settingslib.spaprivileged.model.app.AppListRepository -import com.android.settingslib.spaprivileged.model.app.AppListRepositoryImpl -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext @SearchIndexable -class DataSaverSummary : SettingsPreferenceFragment() { +class DataSaverSummary : DashboardFragment() { private lateinit var switchBar: SettingsMainSwitchBar private lateinit var dataSaverBackend: DataSaverBackend - private lateinit var unrestrictedAccess: Preference // Flag used to avoid infinite loop due if user switch it on/off too quick. private var switching = false @@ -57,8 +45,6 @@ class DataSaverSummary : SettingsPreferenceFragment() { return } - addPreferencesFromResource(R.xml.data_saver) - unrestrictedAccess = findPreference(KEY_UNRESTRICTED_ACCESS)!! dataSaverBackend = DataSaverBackend(requireContext()) } @@ -73,12 +59,14 @@ class DataSaverSummary : SettingsPreferenceFragment() { } } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + use(DataSaverController::class.java).init(viewLifecycleOwner) + } + override fun onResume() { super.onResume() dataSaverBackend.addListener(dataSaverBackendListener) - viewLifecycleOwner.lifecycleScope.launch { - unrestrictedAccess.summary = getUnrestrictedSummary(requireContext()) - } } override fun onPause() { @@ -95,9 +83,10 @@ class DataSaverSummary : SettingsPreferenceFragment() { } } + override fun getPreferenceScreenResId() = R.xml.data_saver override fun getMetricsCategory() = SettingsEnums.DATA_SAVER_SUMMARY - override fun getHelpResource() = R.string.help_url_data_saver + override fun getLogTag() = TAG private val dataSaverBackendListener = object : DataSaverBackend.Listener { override fun onDataSaverChanged(isDataSaving: Boolean) { @@ -109,32 +98,7 @@ class DataSaverSummary : SettingsPreferenceFragment() { } companion object { - private const val KEY_UNRESTRICTED_ACCESS = "unrestricted_access" - - @VisibleForTesting - suspend fun getUnrestrictedSummary( - context: Context, - appListRepository: AppListRepository = - AppListRepositoryImpl(context.applicationContext), - ) = context.formatString( - R.string.data_saver_unrestricted_summary, - "count" to getAllowCount(context.applicationContext, appListRepository), - ) - - private suspend fun getAllowCount(context: Context, appListRepository: AppListRepository) = - withContext(Dispatchers.IO) { - coroutineScope { - val appsDeferred = async { - appListRepository.loadAndFilterApps( - userId = UserHandle.myUserId(), - isSystemApp = false, - ) - } - val uidsAllowed = NetworkPolicyManager.from(context) - .getUidsWithPolicy(NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND) - appsDeferred.await().count { app -> app.uid in uidsAllowed } - } - } + private const val TAG = "DataSaverSummary" private fun Context.isDataSaverVisible(): Boolean = resources.getBoolean(R.bool.config_show_data_saver) diff --git a/tests/robotests/src/com/android/settings/applications/SpecialAppAccessPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/SpecialAppAccessPreferenceControllerTest.java deleted file mode 100644 index da5ada783ad..00000000000 --- a/tests/robotests/src/com/android/settings/applications/SpecialAppAccessPreferenceControllerTest.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2017 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.applications; - -import static com.android.settings.core.BasePreferenceController.AVAILABLE; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.ModuleInfo; -import android.content.pm.PackageManager; - -import androidx.preference.Preference; -import androidx.preference.PreferenceScreen; - -import com.android.settings.R; -import com.android.settings.datausage.AppStateDataUsageBridge; -import com.android.settings.testutils.shadow.ShadowApplicationsState; -import com.android.settings.testutils.shadow.ShadowUserManager; -import com.android.settingslib.applications.ApplicationsState; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; - -import java.util.ArrayList; - -@RunWith(RobolectricTestRunner.class) -@Config(shadows = {ShadowUserManager.class, ShadowApplicationsState.class}) -public class SpecialAppAccessPreferenceControllerTest { - - private Context mContext; - @Mock - private ApplicationsState.Session mSession; - @Mock - private PreferenceScreen mScreen; - @Mock - private PackageManager mPackageManager; - - private SpecialAppAccessPreferenceController mController; - private Preference mPreference; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mContext = spy(RuntimeEnvironment.application); - when(mContext.getApplicationContext()).thenReturn(mContext); - ShadowUserManager.getShadow().setProfileIdsWithDisabled(new int[]{0}); - doReturn(mPackageManager).when(mContext).getPackageManager(); - doReturn(new ArrayList()).when(mPackageManager).getInstalledModules(anyInt()); - mController = new SpecialAppAccessPreferenceController(mContext, "test_key"); - mPreference = new Preference(mContext); - when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); - - mController.mSession = mSession; - } - - @Test - public void getAvailabilityState_unsearchable() { - assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); - } - - @Test - public void updateState_shouldSetSummary() { - final ArrayList apps = new ArrayList<>(); - final ApplicationsState.AppEntry entry = mock(ApplicationsState.AppEntry.class); - entry.hasLauncherEntry = true; - entry.info = new ApplicationInfo(); - entry.extraInfo = new AppStateDataUsageBridge.DataUsageState( - true /* allowlisted */, false /* denylisted */); - apps.add(entry); - when(mSession.getAllApps()).thenReturn(apps); - - mController.displayPreference(mScreen); - mController.onExtraInfoUpdated(); - - assertThat(mPreference.getSummary()) - .isEqualTo(mContext.getResources().getQuantityString( - R.plurals.special_access_summary, 1, 1)); - } - - @Test - public void updateState_wrongExtraInfo_shouldNotIncludeInSummary() { - final ArrayList apps = new ArrayList<>(); - final ApplicationsState.AppEntry entry = mock(ApplicationsState.AppEntry.class); - entry.hasLauncherEntry = true; - entry.info = new ApplicationInfo(); - entry.extraInfo = new AppStateNotificationBridge.NotificationsSentState(); - apps.add(entry); - when(mSession.getAllApps()).thenReturn(apps); - - mController.displayPreference(mScreen); - mController.onExtraInfoUpdated(); - - assertThat(mPreference.getSummary()) - .isEqualTo(mContext.getResources().getQuantityString( - R.plurals.special_access_summary, 0, 0)); - } -} diff --git a/tests/robotests/src/com/android/settings/applications/specialaccess/DataSaverControllerTest.java b/tests/robotests/src/com/android/settings/applications/specialaccess/DataSaverControllerTest.java deleted file mode 100644 index f039c97b2dc..00000000000 --- a/tests/robotests/src/com/android/settings/applications/specialaccess/DataSaverControllerTest.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2017 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.applications.specialaccess; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.content.res.Resources; - -import com.android.settings.R; - -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; - -@RunWith(RobolectricTestRunner.class) -public class DataSaverControllerTest { - - private Context mContext; - private Resources mResources; - private DataSaverController mController; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mContext = spy(RuntimeEnvironment.application.getApplicationContext()); - - mResources = spy(mContext.getResources()); - when(mContext.getResources()).thenReturn(mResources); - - mController = new DataSaverController(mContext, "key"); - } - - @Test - public void testDataSaver_byDefault_shouldBeShown() { - when(mResources.getBoolean(R.bool.config_show_data_saver)).thenReturn(true); - assertThat(mController.isAvailable()).isTrue(); - } - - @Ignore - @Test - @Config(qualifiers = "mcc999") - public void testDataSaver_ifDisabledByCarrier_shouldNotBeShown() { - assertThat(mController.isAvailable()).isFalse(); - } - - @Test - public void testDataSaver_ifDisabled_shouldNotBeShown() { - when(mResources.getBoolean(R.bool.config_show_data_saver)).thenReturn(false); - assertThat(mController.isAvailable()).isFalse(); - } -} diff --git a/tests/spa_unit/src/com/android/settings/datausage/DataSaverSummaryTest.kt b/tests/spa_unit/src/com/android/settings/applications/specialaccess/DataSaverControllerTest.kt similarity index 76% rename from tests/spa_unit/src/com/android/settings/datausage/DataSaverSummaryTest.kt rename to tests/spa_unit/src/com/android/settings/applications/specialaccess/DataSaverControllerTest.kt index 3c88d8e875b..c2413af7603 100644 --- a/tests/spa_unit/src/com/android/settings/datausage/DataSaverSummaryTest.kt +++ b/tests/spa_unit/src/com/android/settings/applications/specialaccess/DataSaverControllerTest.kt @@ -14,15 +14,19 @@ * limitations under the License. */ -package com.android.settings.datausage +package com.android.settings.applications.specialaccess import android.content.Context import android.content.pm.ApplicationInfo +import android.content.res.Resources import android.net.NetworkPolicyManager import android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.android.settings.datausage.DataSaverSummary.Companion.getUnrestrictedSummary +import com.android.settings.R +import com.android.settings.applications.specialaccess.DataSaverController.Companion.getUnrestrictedSummary +import com.android.settings.core.BasePreferenceController.AVAILABLE +import com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE import com.android.settingslib.spaprivileged.model.app.AppListRepository import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -41,20 +45,41 @@ import org.mockito.Mockito.`when` as whenever @OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidJUnit4::class) -class DataSaverSummaryTest { +class DataSaverControllerTest { @get:Rule val mockito: MockitoRule = MockitoJUnit.rule() @Spy private val context: Context = ApplicationProvider.getApplicationContext() + @Spy + private val resources: Resources = context.resources + @Mock private lateinit var networkPolicyManager: NetworkPolicyManager + @Mock + private lateinit var dataSaverController: DataSaverController + @Before fun setUp() { whenever(context.applicationContext).thenReturn(context) + whenever(context.resources).thenReturn(resources) whenever(NetworkPolicyManager.from(context)).thenReturn(networkPolicyManager) + + dataSaverController = DataSaverController(context, "key") + } + + @Test + fun getAvailabilityStatus_whenConfigOn_available() { + whenever(resources.getBoolean(R.bool.config_show_data_saver)).thenReturn(true) + assertThat(dataSaverController.availabilityStatus).isEqualTo(AVAILABLE) + } + + @Test + fun getAvailabilityStatus_whenConfigOff_unsupportedOnDevice() { + whenever(resources.getBoolean(R.bool.config_show_data_saver)).thenReturn(false) + assertThat(dataSaverController.availabilityStatus).isEqualTo(UNSUPPORTED_ON_DEVICE) } @Test From d75bd5298244b63f7f8a7fe0c071c5872a0e09de Mon Sep 17 00:00:00 2001 From: tom hsu Date: Fri, 26 May 2023 13:39:18 +0800 Subject: [PATCH 08/11] [Settings] After APM on, do not show the page result in Settings search Bug: 249520846 Test: Manual test Change-Id: I51df50f244649a406449449a0b2d39164ad5e218 --- .../settings/network/telephony/MobileNetworkSettings.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java index 83d2117af1b..d6d9df4a992 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java +++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java @@ -52,7 +52,6 @@ import com.android.settings.wifi.WifiPickerTrackerHelper; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.mobile.dataservice.MobileNetworkInfoEntity; import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity; -import com.android.settingslib.mobile.dataservice.UiccInfoEntity; import com.android.settingslib.search.SearchIndexable; import com.android.settingslib.utils.ThreadUtils; @@ -441,8 +440,10 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme /** suppress full page if user is not admin */ @Override protected boolean isPageSearchEnabled(Context context) { - return SubscriptionUtil.isSimHardwareVisible(context) && - context.getSystemService(UserManager.class).isAdminUser(); + boolean isAirplaneOff = Settings.Global.getInt(context.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0) == 0; + return isAirplaneOff && SubscriptionUtil.isSimHardwareVisible(context) + && context.getSystemService(UserManager.class).isAdminUser(); } }; From 499a2fa16d5c99ab2e4691a400143ff6a3353828 Mon Sep 17 00:00:00 2001 From: tom hsu Date: Thu, 25 May 2023 20:35:23 +0800 Subject: [PATCH 09/11] [Settings] Add data roaming observer - Data roaming can be controlled by SettingsProvider, it shall be listen the change and set the change state to repository. Bug: 278822324 Test: Manual test passed. Test: atest passed Change-Id: I6eb5330ba643c485c5d12eb879af1e0e8f7baed7 --- .../network/MobileNetworkRepository.java | 55 +++++++++++++++- .../RoamingPreferenceController.java | 63 +++---------------- 2 files changed, 63 insertions(+), 55 deletions(-) diff --git a/src/com/android/settings/network/MobileNetworkRepository.java b/src/com/android/settings/network/MobileNetworkRepository.java index e55ba26e106..f6096982f0c 100644 --- a/src/com/android/settings/network/MobileNetworkRepository.java +++ b/src/com/android/settings/network/MobileNetworkRepository.java @@ -88,7 +88,7 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions private List mMobileNetworkInfoEntityList = new ArrayList<>(); private Context mContext; private AirplaneModeObserver mAirplaneModeObserver; - private Uri mAirplaneModeSettingUri; + private DataRoamingObserver mDataRoamingObserver; private MetricsFeatureProvider mMetricsFeatureProvider; private Map mDataContentObserverMap = new HashMap<>(); private int mPhysicalSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX; @@ -127,10 +127,13 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions mUiccInfoDao = mMobileNetworkDatabase.mUiccInfoDao(); mMobileNetworkInfoDao = mMobileNetworkDatabase.mMobileNetworkInfoDao(); mAirplaneModeObserver = new AirplaneModeObserver(new Handler(Looper.getMainLooper())); - mAirplaneModeSettingUri = Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON); + mDataRoamingObserver = new DataRoamingObserver(new Handler(Looper.getMainLooper())); } private class AirplaneModeObserver extends ContentObserver { + private Uri mAirplaneModeSettingUri = + Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON); + AirplaneModeObserver(Handler handler) { super(handler); } @@ -155,6 +158,46 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions } } + private class DataRoamingObserver extends ContentObserver { + private int mRegSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + private String mBaseField = Settings.Global.DATA_ROAMING; + + DataRoamingObserver(Handler handler) { + super(handler); + } + + public void register(Context context, int subId) { + mRegSubId = subId; + String lastField = mBaseField; + createTelephonyManagerBySubId(subId); + TelephonyManager tm = mTelephonyManagerMap.get(subId); + if (tm.getSimCount() != 1) { + lastField += subId; + } + context.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(lastField), false, this); + } + + public void unRegister(Context context) { + context.getContentResolver().unregisterContentObserver(this); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + TelephonyManager tm = mTelephonyManagerMap.get(mRegSubId); + if (tm == null) { + return; + } + sExecutor.execute(() -> { + insertMobileNetworkInfo(mContext, mRegSubId, tm); + }); + boolean isDataRoamingEnabled = tm.isDataRoamingEnabled(); + for (MobileNetworkCallback callback : sCallbacks) { + callback.onDataRoamingChanged(mRegSubId, isDataRoamingEnabled); + } + } + } + /** * Register all callbacks and listener. * @@ -180,6 +223,7 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { addRegisterBySubId(subId); createTelephonyManagerBySubId(subId); + mDataRoamingObserver.register(mContext, subId); } } @@ -251,6 +295,7 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions if (sCallbacks.isEmpty()) { mSubscriptionManager.removeOnSubscriptionsChangedListener(this); mAirplaneModeObserver.unRegister(mContext); + mDataRoamingObserver.unRegister(mContext); mDataContentObserverMap.forEach((id, observer) -> { observer.unRegister(mContext); }); @@ -709,6 +754,12 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions default void onAirplaneModeChanged(boolean enabled) { } + /** + * Notify clients data roaming changed of subscription. + */ + default void onDataRoamingChanged(int subId, boolean enabled) { + } + default void onCallStateChanged(int state) { } } diff --git a/src/com/android/settings/network/telephony/RoamingPreferenceController.java b/src/com/android/settings/network/telephony/RoamingPreferenceController.java index ff5da52525b..fb8cd519e27 100644 --- a/src/com/android/settings/network/telephony/RoamingPreferenceController.java +++ b/src/com/android/settings/network/telephony/RoamingPreferenceController.java @@ -21,28 +21,23 @@ import static androidx.lifecycle.Lifecycle.Event.ON_STOP; import android.content.Context; import android.os.PersistableBundle; -import android.provider.Settings; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.Log; import androidx.annotation.VisibleForTesting; +import androidx.fragment.app.FragmentManager; import androidx.lifecycle.LifecycleObserver; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.OnLifecycleEvent; -import androidx.annotation.VisibleForTesting; -import androidx.fragment.app.FragmentManager; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; -import com.android.settings.network.GlobalSettingsChangeListener; import com.android.settings.network.MobileNetworkRepository; import com.android.settingslib.RestrictedSwitchPreference; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.mobile.dataservice.MobileNetworkInfoEntity; -import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity; -import com.android.settingslib.mobile.dataservice.UiccInfoEntity; import java.util.ArrayList; import java.util.List; @@ -52,7 +47,6 @@ import java.util.List; */ public class RoamingPreferenceController extends TelephonyTogglePreferenceController implements LifecycleObserver, MobileNetworkRepository.MobileNetworkCallback { - private static final String TAG = "RoamingController"; private static final String DIALOG_TAG = "MobileDataDialog"; @@ -63,15 +57,6 @@ public class RoamingPreferenceController extends TelephonyTogglePreferenceContro protected LifecycleOwner mLifecycleOwner; private List mMobileNetworkInfoEntityList = new ArrayList<>(); - /** - * There're 2 listeners both activated at the same time. - * For project that access DATA_ROAMING, only first listener is functional. - * For project that access "DATA_ROAMING + subId", first listener will be stopped when receiving - * any onChange from second listener. - */ - private GlobalSettingsChangeListener mListener; - private GlobalSettingsChangeListener mListenerForSubId; - @VisibleForTesting FragmentManager mFragmentManager; MobileNetworkInfoEntity mMobileNetworkInfoEntity; @@ -102,34 +87,11 @@ public class RoamingPreferenceController extends TelephonyTogglePreferenceContro public void onStart() { mMobileNetworkRepository.addRegister(mLifecycleOwner, this, mSubId); mMobileNetworkRepository.updateEntity(); - if (mListener == null) { - mListener = new GlobalSettingsChangeListener(mContext, - Settings.Global.DATA_ROAMING) { - public void onChanged(String field) { - updateState(mSwitchPreference); - } - }; - } - stopMonitorSubIdSpecific(); - - if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { - return; - } - - mListenerForSubId = new GlobalSettingsChangeListener(mContext, - Settings.Global.DATA_ROAMING + mSubId) { - public void onChanged(String field) { - stopMonitor(); - updateState(mSwitchPreference); - } - }; } @OnLifecycleEvent(ON_STOP) public void onStop() { mMobileNetworkRepository.removeRegister(this); - stopMonitor(); - stopMonitorSubIdSpecific(); } @Override @@ -219,20 +181,6 @@ public class RoamingPreferenceController extends TelephonyTogglePreferenceContro dialogFragment.show(mFragmentManager, DIALOG_TAG); } - private void stopMonitor() { - if (mListener != null) { - mListener.close(); - mListener = null; - } - } - - private void stopMonitorSubIdSpecific() { - if (mListenerForSubId != null) { - mListenerForSubId.close(); - mListenerForSubId = null; - } - } - @VisibleForTesting public void setMobileNetworkInfoEntity(MobileNetworkInfoEntity mobileNetworkInfoEntity) { mMobileNetworkInfoEntity = mobileNetworkInfoEntity; @@ -251,4 +199,13 @@ public class RoamingPreferenceController extends TelephonyTogglePreferenceContro } }); } + + @Override + public void onDataRoamingChanged(int subId, boolean enabled) { + if (subId != mSubId) { + Log.d(TAG, "onDataRoamingChanged - wrong subId : " + subId + " / " + enabled); + return; + } + update(); + } } From bbef903b882a93e9e78b5d9f59b71587d2921fc0 Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Fri, 26 May 2023 15:32:08 +0000 Subject: [PATCH 10/11] Revert "Update theme for ChannelPanelActivity" This reverts commit 41eeb31b200fa6764432378d974c01c3a17d843a. Reason for revert: b/284380876 Change-Id: Ia06bf047db187091753e560f70427abb5b26378a --- AndroidManifest.xml | 2 +- res/drawable/button_border_selected.xml | 3 +++ res/drawable/button_border_unselected.xml | 2 +- res/values/themes.xml | 3 --- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 08fbf04441d..6fe6832b76a 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -3717,7 +3717,7 @@ diff --git a/res/drawable/button_border_selected.xml b/res/drawable/button_border_selected.xml index 1402380d0ed..0cd4aa54b5a 100644 --- a/res/drawable/button_border_selected.xml +++ b/res/drawable/button_border_selected.xml @@ -15,7 +15,10 @@ limitations under the License. --> + diff --git a/res/drawable/button_border_unselected.xml b/res/drawable/button_border_unselected.xml index d0ce75b8c86..2c2ef3d8d83 100644 --- a/res/drawable/button_border_unselected.xml +++ b/res/drawable/button_border_unselected.xml @@ -18,7 +18,7 @@ android:shape="rectangle"> + android:color="@color/notification_importance_button_unselected"/> diff --git a/res/values/themes.xml b/res/values/themes.xml index 8f132799b79..eeba1c77aca 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -227,9 +227,6 @@ @*android:string/config_headlineFontFamilyMedium -