diff --git a/res/layout/dashboard.xml b/res/layout/dashboard.xml index ccb50ae10ed..8031028d8c6 100644 --- a/res/layout/dashboard.xml +++ b/res/layout/dashboard.xml @@ -26,5 +26,4 @@ android:paddingEnd="@dimen/dashboard_padding_end" android:paddingTop="@dimen/dashboard_padding_top" android:paddingBottom="@dimen/dashboard_padding_bottom" - android:scrollbars="vertical"/> - + android:scrollbars="vertical"/> \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 5bdd578e959..53abd544d50 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -10071,4 +10071,11 @@ Devices + + + High Frequency Panel + + + Enable Virtual High Frequency Panel + diff --git a/res/xml/app_and_notification.xml b/res/xml/app_and_notification.xml index 5a55ba5dab0..c15df75afdc 100644 --- a/res/xml/app_and_notification.xml +++ b/res/xml/app_and_notification.xml @@ -34,18 +34,18 @@ android:title="@string/applications_settings" android:key="all_app_info" android:fragment="com.android.settings.applications.manageapplications.ManageApplications" - android:order="20" /> + android:order="20"/> + android:order="-190"/> + android:order="10"/> - + + android:targetClass="com.android.cellbroadcastreceiver.CellBroadcastSettings"/> + settings:controller="com.android.settings.applications.SpecialAppAccessPreferenceController"/> diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml index a46ff2f9611..8cc8001088b 100644 --- a/res/xml/development_settings.xml +++ b/res/xml/development_settings.xml @@ -359,6 +359,11 @@ android:entries="@array/overlay_display_devices_entries" android:entryValues="@array/overlay_display_devices_values" /> + + diff --git a/res/xml/network_and_internet.xml b/res/xml/network_and_internet.xml index 0d130e06b81..8e36e919220 100644 --- a/res/xml/network_and_internet.xml +++ b/res/xml/network_and_internet.xml @@ -42,10 +42,6 @@ settings:keywords="@string/keywords_more_mobile_networks" settings:userRestriction="no_config_mobile_networks" settings:useAdminDisabledSummary="true"> - createPreferenceControllers(Context context) { final Activity activity = getActivity(); @@ -77,7 +83,6 @@ public class AppAndNotificationDashboardFragment extends DashboardFragment { final List controllers = new ArrayList<>(); controllers.add(new EmergencyBroadcastPreferenceController(context, "app_and_notif_cell_broadcast_settings")); - controllers.add(new SpecialAppAccessPreferenceController(context)); controllers.add(new RecentAppsPreferenceController(context, app, host)); return controllers; } diff --git a/src/com/android/settings/applications/AppStateBaseBridge.java b/src/com/android/settings/applications/AppStateBaseBridge.java index 2329b4469e6..1a39483af9a 100644 --- a/src/com/android/settings/applications/AppStateBaseBridge.java +++ b/src/com/android/settings/applications/AppStateBaseBridge.java @@ -45,7 +45,7 @@ public abstract class AppStateBaseBridge implements ApplicationsState.Callbacks // the same time as us as well. mHandler = new BackgroundHandler(mAppState != null ? mAppState.getBackgroundLooper() : Looper.getMainLooper()); - mMainHandler = new MainHandler(); + mMainHandler = new MainHandler(Looper.getMainLooper()); } public void resume() { @@ -106,11 +106,16 @@ public abstract class AppStateBaseBridge implements ApplicationsState.Callbacks } protected abstract void loadAllExtraInfo(); + protected abstract void updateExtraInfo(AppEntry app, String pkg, int uid); private class MainHandler extends Handler { private static final int MSG_INFO_UPDATED = 1; + public MainHandler(Looper looper) { + super(looper); + } + @Override public void handleMessage(Message msg) { switch (msg.what) { diff --git a/src/com/android/settings/applications/SpecialAppAccessPreferenceController.java b/src/com/android/settings/applications/SpecialAppAccessPreferenceController.java index 16a6cab5d98..a395f9833f0 100644 --- a/src/com/android/settings/applications/SpecialAppAccessPreferenceController.java +++ b/src/com/android/settings/applications/SpecialAppAccessPreferenceController.java @@ -13,43 +13,140 @@ */ package com.android.settings.applications; +import android.app.Application; import android.content.Context; -import com.android.settings.R; -import com.android.settings.core.PreferenceControllerMixin; -import com.android.settings.datausage.DataSaverBackend; -import com.android.settingslib.core.AbstractPreferenceController; - +import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; -public class SpecialAppAccessPreferenceController extends AbstractPreferenceController - implements PreferenceControllerMixin { +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.datausage.AppStateDataUsageBridge; +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; - private static final String KEY_SPECIAL_ACCESS = "special_access"; +import java.util.ArrayList; - private DataSaverBackend mDataSaverBackend; +public class SpecialAppAccessPreferenceController extends BasePreferenceController implements + AppStateBaseBridge.Callback, ApplicationsState.Callbacks, LifecycleObserver, OnStart, + OnStop, OnDestroy { - public SpecialAppAccessPreferenceController(Context context) { - super(context); + @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 boolean isAvailable() { - return true; + public int getAvailabilityStatus() { + return AVAILABLE_UNSEARCHABLE; } @Override - public String getPreferenceKey() { - return KEY_SPECIAL_ACCESS; + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); + } + + @Override + public void onStart() { + mDataUsageBridge.resume(); + } + + @Override + public void onStop() { + mDataUsageBridge.pause(); + } + + @Override + public void onDestroy() { + mDataUsageBridge.release(); } @Override public void updateState(Preference preference) { - if (mDataSaverBackend == null) { - mDataSaverBackend = new DataSaverBackend(mContext); - } - final int count = mDataSaverBackend.getWhitelistedCount(); - preference.setSummary(mContext.getResources().getQuantityString( - R.plurals.special_access_summary, count, count)); + 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 != null && ((AppStateDataUsageBridge.DataUsageState) + entry.extraInfo).isDataSaverWhitelisted) { + 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() { + } + + @Override + public void onLoadEntriesCompleted() { + } + } diff --git a/src/com/android/settings/applications/defaultapps/DefaultHomePreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultHomePreferenceController.java index 872f5a34d80..9fbde3e25b8 100644 --- a/src/com/android/settings/applications/defaultapps/DefaultHomePreferenceController.java +++ b/src/com/android/settings/applications/defaultapps/DefaultHomePreferenceController.java @@ -105,7 +105,7 @@ public class DefaultHomePreferenceController extends DefaultAppPreferenceControl Intent intent = new Intent(Intent.ACTION_APPLICATION_PREFERENCES) .setPackage(packageName) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - return mPackageManager.queryIntentActivities(intent, 0).size() == 1 ? intent : null; + return intent.resolveActivity(mPackageManager) != null ? intent : null; } public static boolean hasHomePreference(String pkg, Context context) { diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java index cd452817d0e..20e702a3eee 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java @@ -98,11 +98,11 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll BluetoothDevice device = mCachedDevice.getDevice(); profilePref.setEnabled(!mCachedDevice.isBusy()); if (profile instanceof MapProfile) { - profilePref.setChecked(mCachedDevice.getMessagePermissionChoice() - == CachedBluetoothDevice.ACCESS_ALLOWED); + profilePref.setChecked(device.getMessageAccessPermission() + == BluetoothDevice.ACCESS_ALLOWED); } else if (profile instanceof PbapServerProfile) { - profilePref.setChecked(mCachedDevice.getPhonebookPermissionChoice() - == CachedBluetoothDevice.ACCESS_ALLOWED); + profilePref.setChecked(device.getPhonebookAccessPermission() + == BluetoothDevice.ACCESS_ALLOWED); } else if (profile instanceof PanProfile) { profilePref.setChecked(profile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED); @@ -130,31 +130,31 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll /** * Helper method to enable a profile for a device. */ - private void enableProfile(LocalBluetoothProfile profile, BluetoothDevice device, - SwitchPreference profilePref) { + private void enableProfile(LocalBluetoothProfile profile) { + final BluetoothDevice bluetoothDevice = mCachedDevice.getDevice(); if (profile instanceof PbapServerProfile) { - mCachedDevice.setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED); + bluetoothDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED); // We don't need to do the additional steps below for this profile. return; } if (profile instanceof MapProfile) { - mCachedDevice.setMessagePermissionChoice(BluetoothDevice.ACCESS_ALLOWED); + bluetoothDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_ALLOWED); } - profile.setPreferred(device, true); + profile.setPreferred(bluetoothDevice, true); mCachedDevice.connectProfile(profile); } /** * Helper method to disable a profile for a device */ - private void disableProfile(LocalBluetoothProfile profile, BluetoothDevice device, - SwitchPreference profilePref) { + private void disableProfile(LocalBluetoothProfile profile) { + final BluetoothDevice bluetoothDevice = mCachedDevice.getDevice(); mCachedDevice.disconnect(profile); - profile.setPreferred(device, false); + profile.setPreferred(bluetoothDevice, false); if (profile instanceof MapProfile) { - mCachedDevice.setMessagePermissionChoice(BluetoothDevice.ACCESS_REJECTED); + bluetoothDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_REJECTED); } else if (profile instanceof PbapServerProfile) { - mCachedDevice.setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_REJECTED); + bluetoothDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED); } } @@ -175,11 +175,10 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll } } SwitchPreference profilePref = (SwitchPreference) preference; - BluetoothDevice device = mCachedDevice.getDevice(); if (profilePref.isChecked()) { - enableProfile(profile, device, profilePref); + enableProfile(profile); } else { - disableProfile(profile, device, profilePref); + disableProfile(profile); } refreshProfilePreference(profilePref, profile); return true; @@ -191,17 +190,18 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll */ private List getProfiles() { List result = mCachedDevice.getConnectableProfiles(); + final BluetoothDevice device = mCachedDevice.getDevice(); - final int pbapPermission = mCachedDevice.getPhonebookPermissionChoice(); + final int pbapPermission = device.getPhonebookAccessPermission(); // Only provide PBAP cabability if the client device has requested PBAP. - if (pbapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN) { + if (pbapPermission != BluetoothDevice.ACCESS_UNKNOWN) { final PbapServerProfile psp = mManager.getProfileManager().getPbapProfile(); result.add(psp); } final MapProfile mapProfile = mManager.getProfileManager().getMapProfile(); - final int mapPermission = mCachedDevice.getMessagePermissionChoice(); - if (mapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN) { + final int mapPermission = device.getMessageAccessPermission(); + if (mapPermission != BluetoothDevice.ACCESS_UNKNOWN) { result.add(mapProfile); } diff --git a/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java b/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java index c452957bc14..c5f62b8b4d6 100644 --- a/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java +++ b/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java @@ -237,42 +237,42 @@ public final class BluetoothPermissionRequest extends BroadcastReceiver { String intentName = BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY; if (mRequestType == BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) { - int phonebookPermission = cachedDevice.getPhonebookPermissionChoice(); + int phonebookPermission = mDevice.getPhonebookAccessPermission(); - if (phonebookPermission == CachedBluetoothDevice.ACCESS_UNKNOWN) { + if (phonebookPermission == BluetoothDevice.ACCESS_UNKNOWN) { // Leave 'processed' as false. - } else if (phonebookPermission == CachedBluetoothDevice.ACCESS_ALLOWED) { + } else if (phonebookPermission == BluetoothDevice.ACCESS_ALLOWED) { sendReplyIntentToReceiver(true); processed = true; - } else if (phonebookPermission == CachedBluetoothDevice.ACCESS_REJECTED) { + } else if (phonebookPermission == BluetoothDevice.ACCESS_REJECTED) { sendReplyIntentToReceiver(false); processed = true; } else { Log.e(TAG, "Bad phonebookPermission: " + phonebookPermission); } } else if (mRequestType == BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS) { - int messagePermission = cachedDevice.getMessagePermissionChoice(); + int messagePermission = mDevice.getMessageAccessPermission(); - if (messagePermission == CachedBluetoothDevice.ACCESS_UNKNOWN) { + if (messagePermission == BluetoothDevice.ACCESS_UNKNOWN) { // Leave 'processed' as false. - } else if (messagePermission == CachedBluetoothDevice.ACCESS_ALLOWED) { + } else if (messagePermission == BluetoothDevice.ACCESS_ALLOWED) { sendReplyIntentToReceiver(true); processed = true; - } else if (messagePermission == CachedBluetoothDevice.ACCESS_REJECTED) { + } else if (messagePermission == BluetoothDevice.ACCESS_REJECTED) { sendReplyIntentToReceiver(false); processed = true; } else { Log.e(TAG, "Bad messagePermission: " + messagePermission); } } else if(mRequestType == BluetoothDevice.REQUEST_TYPE_SIM_ACCESS) { - int simPermission = cachedDevice.getSimPermissionChoice(); + int simPermission = mDevice.getSimAccessPermission(); - if (simPermission == CachedBluetoothDevice.ACCESS_UNKNOWN) { + if (simPermission == BluetoothDevice.ACCESS_UNKNOWN) { // Leave 'processed' as false. - } else if (simPermission == CachedBluetoothDevice.ACCESS_ALLOWED) { + } else if (simPermission == BluetoothDevice.ACCESS_ALLOWED) { sendReplyIntentToReceiver(true); processed = true; - } else if (simPermission == CachedBluetoothDevice.ACCESS_REJECTED) { + } else if (simPermission == BluetoothDevice.ACCESS_REJECTED) { sendReplyIntentToReceiver(false); processed = true; } else { diff --git a/src/com/android/settings/core/FeatureFlags.java b/src/com/android/settings/core/FeatureFlags.java index c6b9d836828..08b4e82e9ca 100644 --- a/src/com/android/settings/core/FeatureFlags.java +++ b/src/com/android/settings/core/FeatureFlags.java @@ -25,4 +25,5 @@ public class FeatureFlags { public static final String AUDIO_SWITCHER_SETTINGS = "settings_audio_switcher"; public static final String DYNAMIC_HOMEPAGE = "settings_dynamic_homepage"; public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid"; + public static final String MOBILE_NETWORK_V2 = "settings_mobile_network_v2"; } diff --git a/src/com/android/settings/datausage/DataSaverBackend.java b/src/com/android/settings/datausage/DataSaverBackend.java index b59da9d7ff8..ed2e6c9195c 100644 --- a/src/com/android/settings/datausage/DataSaverBackend.java +++ b/src/com/android/settings/datausage/DataSaverBackend.java @@ -95,19 +95,10 @@ public class DataSaverBackend { return mUidPolicies.get(uid, POLICY_NONE) == POLICY_ALLOW_METERED_BACKGROUND; } - public int getWhitelistedCount() { - int count = 0; - loadWhitelist(); - for (int i = 0; i < mUidPolicies.size(); i++) { - if (mUidPolicies.valueAt(i) == POLICY_ALLOW_METERED_BACKGROUND) { - count++; - } - } - return count; - } - private void loadWhitelist() { - if (mWhitelistInitialized) return; + if (mWhitelistInitialized) { + return; + } for (int uid : mPolicyManager.getUidsWithPolicy(POLICY_ALLOW_METERED_BACKGROUND)) { mUidPolicies.put(uid, POLICY_ALLOW_METERED_BACKGROUND); @@ -135,7 +126,9 @@ public class DataSaverBackend { } private void loadBlacklist() { - if (mBlacklistInitialized) return; + if (mBlacklistInitialized) { + return; + } for (int uid : mPolicyManager.getUidsWithPolicy(POLICY_REJECT_METERED_BACKGROUND)) { mUidPolicies.put(uid, POLICY_REJECT_METERED_BACKGROUND); } @@ -212,7 +205,9 @@ public class DataSaverBackend { public interface Listener { void onDataSaverChanged(boolean isDataSaving); + void onWhitelistStatusChanged(int uid, boolean isWhitelisted); + void onBlacklistStatusChanged(int uid, boolean isBlacklisted); } } diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index 5be381a04c9..54ca6ef776f 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -439,6 +439,7 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra controllers.add(new TransitionAnimationScalePreferenceController(context)); controllers.add(new AnimatorDurationScalePreferenceController(context)); controllers.add(new SecondaryDisplayPreferenceController(context)); + controllers.add(new HighFrequencyDisplayPreferenceController(context)); controllers.add(new GpuViewUpdatesPreferenceController(context)); controllers.add(new HardwareLayersUpdatesPreferenceController(context)); controllers.add(new DebugGpuOverdrawPreferenceController(context)); diff --git a/src/com/android/settings/development/HighFrequencyDisplayPreferenceController.java b/src/com/android/settings/development/HighFrequencyDisplayPreferenceController.java new file mode 100644 index 00000000000..cbb8d4ce6d0 --- /dev/null +++ b/src/com/android/settings/development/HighFrequencyDisplayPreferenceController.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2018 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.development; + +import android.content.Context; +import android.os.IBinder; +import android.os.Parcel; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.text.TextUtils; + +import com.android.settings.R; +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.development.DeveloperOptionsPreferenceController; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.SwitchPreference; + +public class HighFrequencyDisplayPreferenceController extends DeveloperOptionsPreferenceController + implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin { + + private static final String HIGH_FREQUENCY_DISPLAY_KEY = "high_frequency_display_device"; + + private static final String SURFACE_FLINGER_SERVICE_KEY = "SurfaceFlinger"; + private static final String SURFACE_COMPOSER_INTERFACE_KEY = "android.ui.ISurfaceComposer"; + private static final int SURFACE_FLINGER_HIGH_FREQUENCY_DISPLAY_CODE = 1029; + + private final IBinder mSurfaceFlingerBinder; + + public HighFrequencyDisplayPreferenceController(Context context) { + super(context); + mSurfaceFlingerBinder = ServiceManager.getService(SURFACE_FLINGER_SERVICE_KEY); + } + + @Override + public String getPreferenceKey() { + return HIGH_FREQUENCY_DISPLAY_KEY; + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + Boolean isEnabled = (Boolean) newValue; + writeHighFrequencyDisplaySetting(isEnabled); + ((SwitchPreference) preference).setChecked(isEnabled); + return true; + } + + @Override + public void updateState(Preference preference) { + boolean enableHighFrequencyPanel = readHighFrequencyDisplaySetting(); + ((SwitchPreference) preference).setChecked(enableHighFrequencyPanel); + } + + @Override + protected void onDeveloperOptionsSwitchDisabled() { + super.onDeveloperOptionsSwitchDisabled(); + writeHighFrequencyDisplaySetting(false); + ((SwitchPreference) mPreference).setChecked(false); + } + + @VisibleForTesting + boolean readHighFrequencyDisplaySetting() { + boolean isEnabled = false; + try { + if (mSurfaceFlingerBinder != null) { + final Parcel data = Parcel.obtain(); + final Parcel result = Parcel.obtain(); + data.writeInterfaceToken(SURFACE_COMPOSER_INTERFACE_KEY); + data.writeInt(0); + data.writeInt(0); + mSurfaceFlingerBinder.transact( + SURFACE_FLINGER_HIGH_FREQUENCY_DISPLAY_CODE, + data, result, 0); + + if (result.readInt() != 1 || result.readInt() != 1) { + isEnabled = true; + } + } + } catch (RemoteException ex) { + // intentional no-op + } + return isEnabled; + } + + @VisibleForTesting + void writeHighFrequencyDisplaySetting(boolean isEnabled) { + int multiplier; + int divisor; + + if (isEnabled) { + // 60Hz * 3/2 = 90Hz + multiplier = 2; + divisor = 3; + } else { + multiplier = 1; + divisor = 1; + } + + try { + if (mSurfaceFlingerBinder != null) { + final Parcel data = Parcel.obtain(); + data.writeInterfaceToken(SURFACE_COMPOSER_INTERFACE_KEY); + data.writeInt(multiplier); + data.writeInt(divisor); + mSurfaceFlingerBinder.transact( + SURFACE_FLINGER_HIGH_FREQUENCY_DISPLAY_CODE, + data, null, 0); + } + } catch (RemoteException ex) { + // intentional no-op + } + } +} diff --git a/src/com/android/settings/development/autofill/AutofillLoggingLevelPreferenceController.java b/src/com/android/settings/development/autofill/AutofillLoggingLevelPreferenceController.java index a22295c557f..8618bc542c8 100644 --- a/src/com/android/settings/development/autofill/AutofillLoggingLevelPreferenceController.java +++ b/src/com/android/settings/development/autofill/AutofillLoggingLevelPreferenceController.java @@ -19,6 +19,7 @@ package com.android.settings.development.autofill; import android.content.Context; import android.content.res.Resources; import android.provider.Settings; +import android.util.Log; import android.view.autofill.AutofillManager; import com.android.settings.R; @@ -32,6 +33,7 @@ public final class AutofillLoggingLevelPreferenceController extends DeveloperOptionsPreferenceController implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { + private static final String TAG = "AutofillLoggingLevelPreferenceController"; private static final String AUTOFILL_LOGGING_LEVEL_KEY = "autofill_logging_level"; private final String[] mListValues; @@ -73,6 +75,12 @@ public final class AutofillLoggingLevelPreferenceController } private void updateOptions() { + if (mPreference == null) { + // TODO: there should be a hook on AbstractPreferenceController where we could + // unregister mObserver and avoid this check + Log.v(TAG, "ignoring Settings update because UI is gone"); + return; + } final int level = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.AUTOFILL_LOGGING_LEVEL, AutofillManager.DEFAULT_LOGGING_LEVEL); diff --git a/src/com/android/settings/deviceinfo/StorageWizardFormatConfirm.java b/src/com/android/settings/deviceinfo/StorageWizardFormatConfirm.java index e09e89d92ba..c3d034f5e40 100644 --- a/src/com/android/settings/deviceinfo/StorageWizardFormatConfirm.java +++ b/src/com/android/settings/deviceinfo/StorageWizardFormatConfirm.java @@ -59,7 +59,6 @@ public class StorageWizardFormatConfirm extends InstrumentedDialogFragment { final StorageWizardFormatConfirm fragment = new StorageWizardFormatConfirm(); fragment.setArguments(args); - // TODO (b/111150236) : Need to check it again. fragment.show(activity.getSupportFragmentManager(), TAG_FORMAT_WARNING); } diff --git a/src/com/android/settings/deviceinfo/StorageWizardFormatProgress.java b/src/com/android/settings/deviceinfo/StorageWizardFormatProgress.java index 287cc3f0ec6..2d94597c622 100644 --- a/src/com/android/settings/deviceinfo/StorageWizardFormatProgress.java +++ b/src/com/android/settings/deviceinfo/StorageWizardFormatProgress.java @@ -59,7 +59,6 @@ public class StorageWizardFormatProgress extends StorageWizardBase { setHeaderText(R.string.storage_wizard_format_progress_title, getDiskShortDescription()); setBodyText(R.string.storage_wizard_format_progress_body, getDiskDescription()); - // TODO (b/111151113) : Need to check it again. mTask = (PartitionTask) getLastCustomNonConfigurationInstance(); if (mTask == null) { mTask = new PartitionTask(); @@ -69,7 +68,7 @@ public class StorageWizardFormatProgress extends StorageWizardBase { mTask.setActivity(this); } } - // TODO (b/111151113) : Need to check it again. + @Override public Object onRetainCustomNonConfigurationInstance() { return mTask; diff --git a/src/com/android/settings/homepage/CardContentLoader.java b/src/com/android/settings/homepage/CardContentLoader.java new file mode 100644 index 00000000000..4e1e33ef891 --- /dev/null +++ b/src/com/android/settings/homepage/CardContentLoader.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2018 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.homepage; + +import android.content.Context; + +import androidx.annotation.Nullable; + +import com.android.settingslib.utils.AsyncLoaderCompat; + +import java.util.List; + +//TODO(b/112521307): Implement this to make it work with the card database. +public class CardContentLoader { + + private static final String TAG = "CardContentLoader"; + + private CardContentLoaderListener mListener; + + public interface CardContentLoaderListener { + void onFinishCardLoading(List homepageCards); + } + + public CardContentLoader() { + } + + void setListener(CardContentLoaderListener listener) { + mListener = listener; + } + + private static class CardLoader extends AsyncLoaderCompat> { + + public CardLoader(Context context) { + super(context); + } + + @Override + protected void onDiscardResult(List result) { + + } + + @Nullable + @Override + public List loadInBackground() { + return null; + } + } +} diff --git a/src/com/android/settings/homepage/ControllerRendererPool.java b/src/com/android/settings/homepage/ControllerRendererPool.java new file mode 100644 index 00000000000..b2ac9ecde9f --- /dev/null +++ b/src/com/android/settings/homepage/ControllerRendererPool.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2018 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.homepage; + +import android.content.Context; +import android.util.Log; + +import androidx.collection.ArraySet; + +import java.util.Set; + +/** + * This is a fragment scoped singleton holding a set of {@link HomepageCardController} and + * {@link HomepageCardRenderer}. + */ +public class ControllerRendererPool { + + private static final String TAG = "ControllerRendererPool"; + + private final Set mControllers; + private final Set mRenderers; + + public ControllerRendererPool() { + mControllers = new ArraySet<>(); + mRenderers = new ArraySet<>(); + } + + public T getController(Context context, + @HomepageCard.CardType int cardType) { + final Class clz = + HomepageCardLookupTable.getCardControllerClass(cardType); + for (HomepageCardController controller : mControllers) { + if (controller.getClass() == clz) { + Log.d(TAG, "Controller is already there."); + return (T) controller; + } + } + + final HomepageCardController controller = createCardController(context, clz); + if (controller != null) { + mControllers.add(controller); + } + return (T) controller; + } + + public Set getControllers() { + return mControllers; + } + + public HomepageCardRenderer getRenderer(Context context, @HomepageCard.CardType int cardType) { + final Class clz = + HomepageCardLookupTable.getCardRendererClasses(cardType); + for (HomepageCardRenderer renderer : mRenderers) { + if (renderer.getClass() == clz) { + Log.d(TAG, "Renderer is already there."); + return renderer; + } + } + + final HomepageCardRenderer renderer = createCardRenderer(context, clz); + if (renderer != null) { + mRenderers.add(renderer); + } + return renderer; + } + + private HomepageCardController createCardController(Context context, + Class clz) { + /* + if (ConditionHomepageCardController.class == clz) { + return new ConditionHomepageCardController(context); + } + */ + return null; + } + + private HomepageCardRenderer createCardRenderer(Context context, Class clz) { + //if (ConditionHomepageCardRenderer.class == clz) { + // return new ConditionHomepageCardRenderer(context, this /*controllerRendererPool*/); + //} + + return null; + } + +} diff --git a/src/com/android/settings/homepage/HomepageAdapter.java b/src/com/android/settings/homepage/HomepageAdapter.java new file mode 100644 index 00000000000..b44288337d4 --- /dev/null +++ b/src/com/android/settings/homepage/HomepageAdapter.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2018 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.homepage; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.List; + +public class HomepageAdapter extends RecyclerView.Adapter implements + HomepageCardUpdateListener { + + private static final String TAG = "HomepageAdapter"; + + private final Context mContext; + private final ControllerRendererPool mControllerRendererPool; + + private List mHomepageCards; + private RecyclerView mRecyclerView; + + public HomepageAdapter(Context context, HomepageManager manager) { + mContext = context; + mHomepageCards = new ArrayList<>(); + mControllerRendererPool = manager.getControllerRendererPool(); + setHasStableIds(true); + } + + @Override + public long getItemId(int position) { + return mHomepageCards.get(position).hashCode(); + } + + @Override + public int getItemViewType(int position) { + return mHomepageCards.get(position).getCardType(); + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int cardType) { + final HomepageCardRenderer renderer = mControllerRendererPool.getRenderer(mContext, cardType); + final int viewType = renderer.getViewType(); + final View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false); + + return renderer.createViewHolder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + final int cardType = mHomepageCards.get(position).getCardType(); + final HomepageCardRenderer renderer = mControllerRendererPool.getRenderer(mContext, cardType); + + renderer.bindView(holder, mHomepageCards.get(position)); + } + + @Override + public int getItemCount() { + return mHomepageCards.size(); + } + + @Override + public void onAttachedToRecyclerView(RecyclerView recyclerView) { + super.onAttachedToRecyclerView(recyclerView); + mRecyclerView = recyclerView; + } + + @Override + public void onHomepageCardUpdated(int cardType, List homepageCards) { + //TODO(b/112245748): Should implement a DiffCallback so we can use notifyItemChanged() + // instead. + if (homepageCards == null) { + mHomepageCards.clear(); + } else { + mHomepageCards = homepageCards; + } + notifyDataSetChanged(); + } +} diff --git a/src/com/android/settings/homepage/HomepageCard.java b/src/com/android/settings/homepage/HomepageCard.java new file mode 100644 index 00000000000..1719f57869a --- /dev/null +++ b/src/com/android/settings/homepage/HomepageCard.java @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2018 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.homepage; + +import android.annotation.IntDef; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.text.TextUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Data class representing a {@link HomepageCard}. + */ +public class HomepageCard { + + /** + * Flags indicating the type of the HomepageCard. + */ + @IntDef({CardType.INVALID, CardType.SLICE, CardType.SUGGESTION, CardType.CONDITIONAL}) + @Retention(RetentionPolicy.SOURCE) + public @interface CardType { + int INVALID = -1; + int SLICE = 1; + int SUGGESTION = 2; + int CONDITIONAL = 3; + } + + private final String mName; + @CardType + private final int mCardType; + private final double mScore; + private final String mSliceUri; + private final int mCategory; + private final String mLocalizedToLocale; + private final String mPackageName; + private final String mAppVersion; + private final String mTitleResName; + private final String mTitleText; + private final String mSummaryResName; + private final String mSummaryText; + private final String mIconResName; + private final int mIconResId; + private final String mCardAction; + private final long mExpireTimeMS; + private final Drawable mDrawable; + private final boolean mSupportHalfWidth; + + String getName() { + return mName; + } + + int getCardType() { + return mCardType; + } + + double getScore() { + return mScore; + } + + String getTextSliceUri() { + return mSliceUri; + } + + Uri getSliceUri() { + return Uri.parse(mSliceUri); + } + + int getCategory() { + return mCategory; + } + + String getLocalizedToLocale() { + return mLocalizedToLocale; + } + + String getPackageName() { + return mPackageName; + } + + String getAppVersion() { + return mAppVersion; + } + + String getTitleResName() { + return mTitleResName; + } + + String getTitleText() { + return mTitleText; + } + + String getSummaryResName() { + return mSummaryResName; + } + + String getSummaryText() { + return mSummaryText; + } + + String getIconResName() { + return mIconResName; + } + + int getIconResId() { + return mIconResId; + } + + String getCardAction() { + return mCardAction; + } + + long getExpireTimeMS() { + return mExpireTimeMS; + } + + Drawable getDrawable() { + return mDrawable; + } + + boolean getSupportHalfWidth() { + return mSupportHalfWidth; + } + + HomepageCard(Builder builder) { + mName = builder.mName; + mCardType = builder.mCardType; + mScore = builder.mScore; + mSliceUri = builder.mSliceUri; + mCategory = builder.mCategory; + mLocalizedToLocale = builder.mLocalizedToLocale; + mPackageName = builder.mPackageName; + mAppVersion = builder.mAppVersion; + mTitleResName = builder.mTitleResName; + mTitleText = builder.mTitleText; + mSummaryResName = builder.mSummaryResName; + mSummaryText = builder.mSummaryText; + mIconResName = builder.mIconResName; + mIconResId = builder.mIconResId; + mCardAction = builder.mCardAction; + mExpireTimeMS = builder.mExpireTimeMS; + mDrawable = builder.mDrawable; + mSupportHalfWidth = builder.mSupportHalfWidth; + } + + @Override + public int hashCode() { + return mName.hashCode(); + } + + /** + * Note that {@link #mName} is treated as a primary key for this class and determines equality. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof HomepageCard)) { + return false; + } + final HomepageCard that = (HomepageCard) obj; + + return TextUtils.equals(mName, that.mName); + } + + static class Builder { + private String mName; + private int mCardType; + private double mScore; + private String mSliceUri; + private int mCategory; + private String mLocalizedToLocale; + private String mPackageName; + private String mAppVersion; + private String mTitleResName; + private String mTitleText; + private String mSummaryResName; + private String mSummaryText; + private String mIconResName; + private int mIconResId; + private String mCardAction; + private long mExpireTimeMS; + private Drawable mDrawable; + private boolean mSupportHalfWidth; + + public Builder setName(String name) { + mName = name; + return this; + } + + public Builder setCardType(int cardType) { + mCardType = cardType; + return this; + } + + public Builder setScore(double score) { + mScore = score; + return this; + } + + public Builder setSliceUri(String sliceUri) { + mSliceUri = sliceUri; + return this; + } + + public Builder setCategory(int category) { + mCategory = category; + return this; + } + + public Builder setLocalizedToLocale(String localizedToLocale) { + mLocalizedToLocale = localizedToLocale; + return this; + } + + public Builder setPackageName(String packageName) { + mPackageName = packageName; + return this; + } + + public Builder setAppVersion(String appVersion) { + mAppVersion = appVersion; + return this; + } + + public Builder setTitleResName(String titleResName) { + mTitleResName = titleResName; + return this; + } + + public Builder setTitleText(String titleText) { + mTitleText = titleText; + return this; + } + + public Builder setSummaryResName(String summaryResName) { + mSummaryResName = summaryResName; + return this; + } + + public Builder setSummaryText(String summaryText) { + mSummaryText = summaryText; + return this; + } + + public Builder setIconResName(String iconResName) { + mIconResName = iconResName; + return this; + } + + public Builder setIconResId(int iconResId) { + mIconResId = iconResId; + return this; + } + + public Builder setCardAction(String cardAction) { + mCardAction = cardAction; + return this; + } + + public Builder setExpireTimeMS(long expireTimeMS) { + mExpireTimeMS = expireTimeMS; + return this; + } + + public Builder setDrawable(Drawable drawable) { + mDrawable = drawable; + return this; + } + + public Builder setSupportHalfWidth(boolean supportHalfWidth) { + mSupportHalfWidth = supportHalfWidth; + return this; + } + + public HomepageCard build() { + return new HomepageCard(this); + } + } +} diff --git a/src/com/android/settings/homepage/HomepageCardController.java b/src/com/android/settings/homepage/HomepageCardController.java new file mode 100644 index 00000000000..c35c3c80c61 --- /dev/null +++ b/src/com/android/settings/homepage/HomepageCardController.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2018 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.homepage; + +import com.android.settingslib.core.lifecycle.Lifecycle; + +import java.util.List; + +//TODO(b/111821137): add test cases +/** + * Data controller for {@link HomepageCard}. + */ +public interface HomepageCardController { + + @HomepageCard.CardType + int getCardType(); + + /** + * When data is updated or changed, the new data should be passed to HomepageManager for list + * updating. + */ + void onDataUpdated(List cardList); + + void onPrimaryClick(HomepageCard card); + + void onActionClick(HomepageCard card); + + void setLifecycle(Lifecycle lifecycle); + + void setHomepageCardUpdateListener(HomepageCardUpdateListener listener); +} diff --git a/src/com/android/settings/homepage/HomepageCardLookupTable.java b/src/com/android/settings/homepage/HomepageCardLookupTable.java new file mode 100644 index 00000000000..9e941e9dd3f --- /dev/null +++ b/src/com/android/settings/homepage/HomepageCardLookupTable.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2018 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.homepage; + +import com.android.settings.homepage.HomepageCard.CardType; + +import java.util.Set; +import java.util.TreeSet; + +public class HomepageCardLookupTable { + + static class HomepageMapping implements Comparable { + @CardType + private final int mCardType; + private final Class mControllerClass; + private final Class mRendererClass; + + private HomepageMapping(@CardType int cardType, + Class controllerClass, + Class rendererClass) { + mCardType = cardType; + mControllerClass = controllerClass; + mRendererClass = rendererClass; + } + + @Override + public int compareTo(HomepageMapping other) { + return Integer.compare(this.mCardType, other.mCardType); + } + } + + private static final Set LOOKUP_TABLE = new TreeSet() { + { + //add(new HomepageMapping(CardType.CONDITIONAL, ConditionHomepageCardController.class, + // ConditionHomepageCardRenderer.class)); + } + }; + + public static Class getCardControllerClass( + @CardType int cardType) { + for (HomepageMapping mapping : LOOKUP_TABLE) { + if (mapping.mCardType == cardType) { + return mapping.mControllerClass; + } + } + return null; + } + + //TODO(b/112578070): Implement multi renderer cases. + public static Class getCardRendererClasses( + @CardType int cardType) { + for (HomepageMapping mapping : LOOKUP_TABLE) { + if (mapping.mCardType == cardType) { + return mapping.mRendererClass; + } + } + return null; + } +} diff --git a/src/com/android/settings/homepage/HomepageCardRenderer.java b/src/com/android/settings/homepage/HomepageCardRenderer.java new file mode 100644 index 00000000000..ffa54e36a69 --- /dev/null +++ b/src/com/android/settings/homepage/HomepageCardRenderer.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018 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.homepage; + +import android.view.View; + +import androidx.recyclerview.widget.RecyclerView; + +/** + * UI renderer for {@link HomepageCard}. + */ +public interface HomepageCardRenderer { + + /** + * The layout type of the controller. + */ + int getViewType(); + + /** + * When {@link HomepageAdapter} calls {@link HomepageAdapter#onCreateViewHolder(ViewGroup, + * int)}, this method will be called to retrieve the corresponding + * {@link androidx.recyclerview.widget.RecyclerView.ViewHolder}. + */ + RecyclerView.ViewHolder createViewHolder(View view); + + /** + * When {@link HomepageAdapter} calls {@link HomepageAdapter#onBindViewHolder(RecyclerView + * .ViewHolder, int)}, this method will be called to bind data to the + * {@link androidx.recyclerview.widget.RecyclerView.ViewHolder}. + */ + void bindView(RecyclerView.ViewHolder holder, HomepageCard card); +} \ No newline at end of file diff --git a/src/com/android/settings/homepage/HomepageCardUpdateListener.java b/src/com/android/settings/homepage/HomepageCardUpdateListener.java new file mode 100644 index 00000000000..a44ba2be6e3 --- /dev/null +++ b/src/com/android/settings/homepage/HomepageCardUpdateListener.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2018 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.homepage; + +import java.util.List; + +/** + * When {@link HomepageCardController} detects changes, it will notify the listeners registered. In + * our case, {@link HomepageManager} gets noticed. + * + * After the list of {@link HomepageCard} gets updated in{@link HomepageManager}, + * {@link HomepageManager} will notify the listeners registered, {@link HomepageAdapter} in this + * case. + */ +interface HomepageCardUpdateListener { + void onHomepageCardUpdated(int cardType, List updateList); +} \ No newline at end of file diff --git a/src/com/android/settings/homepage/HomepageFragment.java b/src/com/android/settings/homepage/HomepageFragment.java index dc6a91fe27d..402725ebc90 100644 --- a/src/com/android/settings/homepage/HomepageFragment.java +++ b/src/com/android/settings/homepage/HomepageFragment.java @@ -48,6 +48,7 @@ public class HomepageFragment extends InstrumentedFragment { private static final String SAVE_BOTTOM_FRAGMENT_LOADED = "bottom_fragment_loaded"; private RecyclerView mCardsContainer; + private HomepageAdapter mHomepageAdapter; private LinearLayoutManager mLayoutManager; private FloatingActionButton mSearchButton; @@ -55,6 +56,14 @@ public class HomepageFragment extends InstrumentedFragment { private View mBottomBar; private View mSearchBar; private boolean mBottomFragmentLoaded; + private HomepageManager mHomepageManager; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mHomepageManager = new HomepageManager(getContext(), getSettingsLifecycle()); + mHomepageManager.startCardContentLoading(); + } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, @@ -65,6 +74,9 @@ public class HomepageFragment extends InstrumentedFragment { //TODO(b/111822407): May have to swap to GridLayoutManager mLayoutManager = new LinearLayoutManager(getActivity()); mCardsContainer.setLayoutManager(mLayoutManager); + mHomepageAdapter = new HomepageAdapter(getContext(), mHomepageManager); + mCardsContainer.setAdapter(mHomepageAdapter); + mHomepageManager.setListener(mHomepageAdapter); return rootView; } diff --git a/src/com/android/settings/homepage/HomepageManager.java b/src/com/android/settings/homepage/HomepageManager.java new file mode 100644 index 00000000000..cbd5841d2cf --- /dev/null +++ b/src/com/android/settings/homepage/HomepageManager.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2018 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.homepage; + +import android.content.Context; +import android.widget.BaseAdapter; + +import com.android.settingslib.core.lifecycle.Lifecycle; + +import java.util.ArrayList; +import java.util.List; + +/** + * This is a centralized manager of multiple {@link HomepageCardController}. + * + * {@link HomepageManager} first loads data from {@link CardContentLoader} and gets back a list of + * {@link HomepageCard}. All subclasses of {@link HomepageCardController} are loaded here, which + * will then trigger the {@link HomepageCardController} to load its data and listen to + * corresponding changes. When every single {@link HomepageCardController} updates its data, the + * data will be passed here, then going through some sorting mechanisms. The + * {@link HomepageCardController} will end up building a list of {@link HomepageCard} for {@link + * HomepageAdapter} and {@link BaseAdapter#notifyDataSetChanged()} will be called to get the page + * refreshed. + */ +public class HomepageManager implements CardContentLoader.CardContentLoaderListener, + HomepageCardUpdateListener { + + private static final String TAG = "HomepageManager"; + //The list for Settings Custom Card + @HomepageCard.CardType + private static final int[] SETTINGS_CARDS = {HomepageCard.CardType.CONDITIONAL}; + + private final Context mContext; + private final ControllerRendererPool mControllerRendererPool; + private final Lifecycle mLifecycle; + + private List mHomepageCards; + private HomepageCardUpdateListener mListener; + + + public HomepageManager(Context context, Lifecycle lifecycle) { + mContext = context; + mLifecycle = lifecycle; + mHomepageCards = new ArrayList<>(); + mControllerRendererPool = new ControllerRendererPool(); + } + + void startCardContentLoading() { + final CardContentLoader cardContentLoader = new CardContentLoader(); + cardContentLoader.setListener(this); + } + + private void loadCardControllers() { + if (mHomepageCards != null) { + for (HomepageCard card : mHomepageCards) { + setupController(card.getCardType()); + } + } + + //for data provided by Settings + for (int cardType : SETTINGS_CARDS) { + setupController(cardType); + } + } + + private void setupController(int cardType) { + final HomepageCardController controller = mControllerRendererPool.getController(mContext, + cardType); + if (controller != null) { + controller.setHomepageCardUpdateListener(this); + controller.setLifecycle(mLifecycle); + } + } + + //TODO(b/111822376): implement sorting mechanism. + private void sortCards() { + //take mHomepageCards as the source and do the ranking based on the rule. + } + + @Override + public void onHomepageCardUpdated(int cardType, List updateList) { + //TODO(b/112245748): Should implement a DiffCallback. + //Keep the old list for comparison. + final List prevCards = mHomepageCards; + + //Remove the existing data that matches the certain cardType so as to insert the new data. + for (int i = mHomepageCards.size() - 1; i >= 0; i--) { + if (mHomepageCards.get(i).getCardType() == cardType) { + mHomepageCards.remove(i); + } + } + + //Append the new data + mHomepageCards.addAll(updateList); + + sortCards(); + + if (mListener != null) { + mListener.onHomepageCardUpdated(HomepageCard.CardType.INVALID, mHomepageCards); + } + } + + @Override + public void onFinishCardLoading(List homepageCards) { + mHomepageCards = homepageCards; + + //Force card sorting here in case CardControllers of custom view have nothing to update + // for the first launch. + sortCards(); + + loadCardControllers(); + } + + void setListener(HomepageCardUpdateListener listener) { + mListener = listener; + } + + public ControllerRendererPool getControllerRendererPool() { + return mControllerRendererPool; + } +} diff --git a/src/com/android/settings/homepage/conditional/BackgroundDataConditionController.java b/src/com/android/settings/homepage/conditional/BackgroundDataConditionController.java index d7c06987e74..0dc3cf124f9 100644 --- a/src/com/android/settings/homepage/conditional/BackgroundDataConditionController.java +++ b/src/com/android/settings/homepage/conditional/BackgroundDataConditionController.java @@ -28,9 +28,14 @@ public class BackgroundDataConditionController implements ConditionalCardControl static final int ID = Objects.hash("BackgroundDataConditionController"); private final Context mAppContext; + private final ConditionManager mConditionManager; + private final NetworkPolicyManager mNetworkPolicyManager; - public BackgroundDataConditionController(Context appContext) { + public BackgroundDataConditionController(Context appContext, ConditionManager manager) { mAppContext = appContext; + mConditionManager = manager; + mNetworkPolicyManager = + (NetworkPolicyManager) appContext.getSystemService(Context.NETWORK_POLICY_SERVICE); } @Override @@ -40,7 +45,7 @@ public class BackgroundDataConditionController implements ConditionalCardControl @Override public boolean isDisplayable() { - return NetworkPolicyManager.from(mAppContext).getRestrictBackground(); + return mNetworkPolicyManager.getRestrictBackground(); } @Override @@ -50,7 +55,8 @@ public class BackgroundDataConditionController implements ConditionalCardControl @Override public void onActionClick() { - NetworkPolicyManager.from(mAppContext).setRestrictBackground(false); + mNetworkPolicyManager.setRestrictBackground(false); + mConditionManager.onConditionChanged(); } @Override diff --git a/src/com/android/settings/homepage/conditional/ConditionManager.java b/src/com/android/settings/homepage/conditional/ConditionManager.java index d6e50123a52..d1c9a27771c 100644 --- a/src/com/android/settings/homepage/conditional/ConditionManager.java +++ b/src/com/android/settings/homepage/conditional/ConditionManager.java @@ -100,7 +100,6 @@ public class ConditionManager { */ public void onActionClick(long id) { getController(id).onActionClick(); - onConditionChanged(); } /** @@ -155,15 +154,16 @@ public class ConditionManager { private void initCandidates() { // Initialize controllers first. mCardControllers.add(new AirplaneModeConditionController(mAppContext, this /* manager */)); - mCardControllers.add(new BackgroundDataConditionController(mAppContext)); + mCardControllers.add( + new BackgroundDataConditionController(mAppContext, this /* manager */)); mCardControllers.add(new BatterySaverConditionController(mAppContext, this /* manager */)); mCardControllers.add(new CellularDataConditionController(mAppContext, this /* manager */)); mCardControllers.add(new DndConditionCardController(mAppContext, this /* manager */)); mCardControllers.add(new HotspotConditionController(mAppContext, this /* manager */)); - mCardControllers.add(new NightDisplayConditionController(mAppContext)); + mCardControllers.add(new NightDisplayConditionController(mAppContext, this /* manager */)); mCardControllers.add(new RingerVibrateConditionController(mAppContext, this /* manager */)); mCardControllers.add(new RingerMutedConditionController(mAppContext, this /* manager */)); - mCardControllers.add(new WorkModeConditionController(mAppContext)); + mCardControllers.add(new WorkModeConditionController(mAppContext, this /* manager */)); // Initialize ui model later. UI model depends on controller. mCandidates.add(new AirplaneModeConditionCard(mAppContext)); diff --git a/src/com/android/settings/homepage/conditional/NightDisplayConditionController.java b/src/com/android/settings/homepage/conditional/NightDisplayConditionController.java index 428fe488989..b4816f15a05 100644 --- a/src/com/android/settings/homepage/conditional/NightDisplayConditionController.java +++ b/src/com/android/settings/homepage/conditional/NightDisplayConditionController.java @@ -30,10 +30,12 @@ public class NightDisplayConditionController implements ConditionalCardControlle ColorDisplayController.Callback { static final int ID = Objects.hash("NightDisplayConditionController"); + private final ConditionManager mConditionManager; private final ColorDisplayController mController; - public NightDisplayConditionController(Context appContext) { + public NightDisplayConditionController(Context appContext, ConditionManager manager) { mController = new ColorDisplayController(appContext); + mConditionManager = manager; } @Override @@ -69,4 +71,9 @@ public class NightDisplayConditionController implements ConditionalCardControlle public void stopMonitoringStateChange() { mController.setListener(null); } + + @Override + public void onActivated(boolean activated) { + mConditionManager.onConditionChanged(); + } } diff --git a/src/com/android/settings/homepage/conditional/WorkModeConditionController.java b/src/com/android/settings/homepage/conditional/WorkModeConditionController.java index 1bd227a0912..033a6a86070 100644 --- a/src/com/android/settings/homepage/conditional/WorkModeConditionController.java +++ b/src/com/android/settings/homepage/conditional/WorkModeConditionController.java @@ -16,11 +16,14 @@ package com.android.settings.homepage.conditional; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.UserInfo; import android.os.UserHandle; import android.os.UserManager; +import android.text.TextUtils; import com.android.settings.Settings; @@ -31,13 +34,25 @@ public class WorkModeConditionController implements ConditionalCardController { static final int ID = Objects.hash("WorkModeConditionController"); + private static final IntentFilter FILTER = new IntentFilter(); + + static { + FILTER.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); + FILTER.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); + } + private final Context mAppContext; private final UserManager mUm; + private final ConditionManager mConditionManager; + private final Receiver mReceiver; + private UserHandle mUserHandle; - public WorkModeConditionController(Context appContext) { + public WorkModeConditionController(Context appContext, ConditionManager manager) { mAppContext = appContext; mUm = mAppContext.getSystemService(UserManager.class); + mConditionManager = manager; + mReceiver = new Receiver(); } @Override @@ -66,12 +81,12 @@ public class WorkModeConditionController implements ConditionalCardController { @Override public void startMonitoringStateChange() { - + mAppContext.registerReceiver(mReceiver, FILTER); } @Override public void stopMonitoringStateChange() { - + mAppContext.unregisterReceiver(mReceiver); } private void updateUserHandle() { @@ -87,4 +102,15 @@ public class WorkModeConditionController implements ConditionalCardController { } } } + + public class Receiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (TextUtils.equals(action, Intent.ACTION_MANAGED_PROFILE_AVAILABLE) + || TextUtils.equals(action, Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)) { + mConditionManager.onConditionChanged(); + } + } + } } diff --git a/src/com/android/settings/network/MobileNetworkPreferenceController.java b/src/com/android/settings/network/MobileNetworkPreferenceController.java index 16d382c4264..06d3f87016c 100644 --- a/src/com/android/settings/network/MobileNetworkPreferenceController.java +++ b/src/com/android/settings/network/MobileNetworkPreferenceController.java @@ -19,6 +19,7 @@ import static android.os.UserHandle.myUserId; import static android.os.UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -27,7 +28,9 @@ import android.provider.Settings; import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.TelephonyManager; +import android.util.FeatureFlagUtils; +import com.android.settings.core.FeatureFlags; import com.android.settings.core.PreferenceControllerMixin; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedPreference; @@ -44,7 +47,12 @@ import androidx.preference.PreferenceScreen; public class MobileNetworkPreferenceController extends AbstractPreferenceController implements PreferenceControllerMixin, LifecycleObserver, OnStart, OnStop { - private static final String KEY_MOBILE_NETWORK_SETTINGS = "mobile_network_settings"; + @VisibleForTesting + static final String KEY_MOBILE_NETWORK_SETTINGS = "mobile_network_settings"; + @VisibleForTesting + static final String MOBILE_NETWORK_PACKAGE = "com.android.phone"; + @VisibleForTesting + static final String MOBILE_NETWORK_CLASS = "com.android.phone.MobileNetworkSettings"; private final boolean mIsSecondaryUser; private final TelephonyManager mTelephonyManager; @@ -134,6 +142,22 @@ public class MobileNetworkPreferenceController extends AbstractPreferenceControl mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0) == 0); } + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (KEY_MOBILE_NETWORK_SETTINGS.equals(preference.getKey())) { + if (FeatureFlagUtils.isEnabled(mContext, FeatureFlags.MOBILE_NETWORK_V2)) { + //TODO(b/110260193): go to the mobile network page existed in settings + } else { + final Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setComponent( + new ComponentName(MOBILE_NETWORK_PACKAGE, MOBILE_NETWORK_CLASS)); + mContext.startActivity(intent); + } + return true; + } + return false; + } + @Override public CharSequence getSummary() { return mTelephonyManager.getNetworkOperatorName(); diff --git a/src/com/android/settings/widget/ValidatedEditTextPreference.java b/src/com/android/settings/widget/ValidatedEditTextPreference.java index 76d8bcceb94..3204ab3894a 100644 --- a/src/com/android/settings/widget/ValidatedEditTextPreference.java +++ b/src/com/android/settings/widget/ValidatedEditTextPreference.java @@ -93,7 +93,8 @@ public class ValidatedEditTextPreference extends CustomEditTextPreferenceCompat textView.setInputType( InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); } else { - textView.setInputType(InputType.TYPE_CLASS_TEXT); + textView.setInputType( + InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); } } diff --git a/tests/robotests/src/com/android/settings/applications/AppAndNotificationDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/applications/AppAndNotificationDashboardFragmentTest.java deleted file mode 100644 index c332c064da3..00000000000 --- a/tests/robotests/src/com/android/settings/applications/AppAndNotificationDashboardFragmentTest.java +++ /dev/null @@ -1,65 +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.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.os.UserManager; - -import com.android.settings.notification.EmergencyBroadcastPreferenceController; -import com.android.settings.testutils.SettingsRobolectricTestRunner; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; - -import java.util.List; - -@RunWith(SettingsRobolectricTestRunner.class) -public class AppAndNotificationDashboardFragmentTest { - - @Test - @Config(shadows = {ShadowEmergencyBroadcastPreferenceController.class}) - public void getNonIndexableKeys_shouldIncludeSpecialAppAccess() { - final Context context = spy(RuntimeEnvironment.application); - UserManager manager = mock(UserManager.class); - when(manager.isAdminUser()).thenReturn(true); - when(context.getSystemService(Context.USER_SERVICE)).thenReturn(manager); - final List niks = AppAndNotificationDashboardFragment.SEARCH_INDEX_DATA_PROVIDER - .getNonIndexableKeys(context); - - assertThat(niks).contains( - new SpecialAppAccessPreferenceController(context).getPreferenceKey()); - } - - @Implements(EmergencyBroadcastPreferenceController.class) - public static class ShadowEmergencyBroadcastPreferenceController { - - @Implementation - public boolean isAvailable() { - return true; - } - } -} diff --git a/tests/robotests/src/com/android/settings/applications/SpecialAppAccessPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/SpecialAppAccessPreferenceControllerTest.java index 224a8f9561b..b6429151b67 100644 --- a/tests/robotests/src/com/android/settings/applications/SpecialAppAccessPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/applications/SpecialAppAccessPreferenceControllerTest.java @@ -16,15 +16,25 @@ package com.android.settings.applications; +import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE; + import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.verify; + +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.pm.ApplicationInfo; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; import com.android.settings.R; -import com.android.settings.datausage.DataSaverBackend; +import com.android.settings.datausage.AppStateDataUsageBridge; import com.android.settings.testutils.SettingsRobolectricTestRunner; +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; @@ -32,48 +42,56 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; -import org.robolectric.util.ReflectionHelpers; +import org.robolectric.annotation.Config; -import androidx.preference.Preference; +import java.util.ArrayList; @RunWith(SettingsRobolectricTestRunner.class) +@Config(shadows = {ShadowUserManager.class, ShadowApplicationsState.class}) public class SpecialAppAccessPreferenceControllerTest { private Context mContext; @Mock - private DataSaverBackend mBackend; + private ApplicationsState.Session mSession; @Mock - private Preference mPreference; + private PreferenceScreen mScreen; private SpecialAppAccessPreferenceController mController; + private Preference mPreference; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; - mController = new SpecialAppAccessPreferenceController(mContext); - ReflectionHelpers.setField(mController, "mDataSaverBackend", mBackend); + ShadowUserManager.getShadow().setProfileIdsWithDisabled(new int[]{0}); + mController = new SpecialAppAccessPreferenceController(mContext, "test_key"); + mPreference = new Preference(mContext); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); + + mController.mSession = mSession; } @Test - public void isAvailable_shouldAlwaysReturnTrue() { - assertThat(mController.isAvailable()).isTrue(); + public void getAvailabilityState_unsearchable() { + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE_UNSEARCHABLE); } @Test public void updateState_shouldSetSummary() { - when(mBackend.getWhitelistedCount()).thenReturn(0); + 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 /* whitelisted */, false /* blacklisted */); + apps.add(entry); + when(mSession.getAllApps()).thenReturn(apps); - mController.updateState(mPreference); + mController.displayPreference(mScreen); + mController.onExtraInfoUpdated(); - verify(mPreference).setSummary(mContext.getResources() - .getQuantityString(R.plurals.special_access_summary, 0, 0)); - - when(mBackend.getWhitelistedCount()).thenReturn(1); - - mController.updateState(mPreference); - - verify(mPreference).setSummary(mContext.getResources() - .getQuantityString(R.plurals.special_access_summary, 1, 1)); + assertThat(mPreference.getSummary()) + .isEqualTo(mContext.getResources().getQuantityString( + R.plurals.special_access_summary, 1, 1)); } } diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultHomePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultHomePreferenceControllerTest.java index ce9fe2a837e..1f623211391 100644 --- a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultHomePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultHomePreferenceControllerTest.java @@ -17,8 +17,8 @@ package com.android.settings.applications.defaultapps; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyList; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; @@ -29,6 +29,8 @@ import static org.mockito.Mockito.when; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.UserManager; @@ -45,14 +47,14 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.util.ReflectionHelpers; -import java.util.Arrays; -import java.util.Collections; - import androidx.preference.Preference; @RunWith(SettingsRobolectricTestRunner.class) public class DefaultHomePreferenceControllerTest { + private static final String TEST_PACKAGE = "test.pkg"; + private static final String TEST_CLASS = "class"; + @Mock private UserManager mUserManager; @Mock(answer = Answers.RETURNS_DEEP_STUBS) @@ -107,14 +109,14 @@ public class DefaultHomePreferenceControllerTest { @Test public void testIsHomeDefault_noDefaultSet_shouldReturnTrue() { when(mPackageManager.getHomeActivities(anyList())).thenReturn(null); - assertThat(DefaultHomePreferenceController.isHomeDefault("test.pkg", mPackageManager)) + assertThat(DefaultHomePreferenceController.isHomeDefault(TEST_PACKAGE, mPackageManager)) .isTrue(); } @Test public void testIsHomeDefault_defaultSetToPkg_shouldReturnTrue() { - final String pkgName = "test.pkg"; - final ComponentName defaultHome = new ComponentName(pkgName, "class"); + final String pkgName = TEST_PACKAGE; + final ComponentName defaultHome = new ComponentName(pkgName, TEST_CLASS); when(mPackageManager.getHomeActivities(anyList())).thenReturn(defaultHome); @@ -124,8 +126,8 @@ public class DefaultHomePreferenceControllerTest { @Test public void testIsHomeDefault_defaultSetToOtherPkg_shouldReturnFalse() { - final String pkgName = "test.pkg"; - final ComponentName defaultHome = new ComponentName("not" + pkgName, "class"); + final String pkgName = TEST_PACKAGE; + final ComponentName defaultHome = new ComponentName("not" + pkgName, TEST_CLASS); when(mPackageManager.getHomeActivities(anyList())).thenReturn(defaultHome); @@ -136,29 +138,28 @@ public class DefaultHomePreferenceControllerTest { @Test public void testGetSettingIntent_homeHasNoSetting_shouldNotReturnSettingIntent() { when(mPackageManager.getHomeActivities(anyList())) - .thenReturn(new ComponentName("test.pkg", "class")); + .thenReturn(new ComponentName(TEST_PACKAGE, TEST_CLASS)); + when(mPackageManager.resolveActivity(any(Intent.class), anyInt())) + .thenReturn(null); + assertThat(mController.getSettingIntent(mController.getDefaultAppInfo())).isNull(); } @Test public void testGetSettingIntent_homeHasOneSetting_shouldReturnSettingIntent() { when(mPackageManager.getHomeActivities(anyList())) - .thenReturn(new ComponentName("test.pkg", "class")); - when(mPackageManager.queryIntentActivities(any(), eq(0))) - .thenReturn(Collections.singletonList(mock(ResolveInfo.class))); + .thenReturn(new ComponentName(TEST_PACKAGE, TEST_CLASS)); + final ResolveInfo info = mock(ResolveInfo.class); + info.activityInfo = mock(ActivityInfo.class); + info.activityInfo.name = TEST_CLASS; + info.activityInfo.applicationInfo = mock(ApplicationInfo.class); + info.activityInfo.applicationInfo.packageName = TEST_PACKAGE; + when(mPackageManager.resolveActivity(any(Intent.class), anyInt())) + .thenReturn(info); Intent intent = mController.getSettingIntent(mController.getDefaultAppInfo()); assertThat(intent).isNotNull(); - assertThat(intent.getPackage()).isEqualTo("test.pkg"); - } - - @Test - public void testGetSettingIntent_homeHasMultipleSettings_shouldNotReturnSettingIntent() { - when(mPackageManager.getHomeActivities(anyList())) - .thenReturn(new ComponentName("test.pkg", "class")); - when(mPackageManager.queryIntentActivities(any(), eq(0))) - .thenReturn(Arrays.asList(mock(ResolveInfo.class), mock(ResolveInfo.class))); - assertThat(mController.getSettingIntent(mController.getDefaultAppInfo())).isNull(); + assertThat(intent.getPackage()).isEqualTo(TEST_PACKAGE); } @Test diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java index 62414e4b52f..848cdffe982 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java @@ -31,8 +31,8 @@ import android.content.Context; import com.android.settings.R; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.SettingsShadowBluetoothDevice; +import com.android.settings.testutils.shadow.ShadowBluetoothDevice; import com.android.settingslib.bluetooth.A2dpProfile; -import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfile; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; @@ -55,7 +55,7 @@ import androidx.preference.PreferenceCategory; import androidx.preference.SwitchPreference; @RunWith(SettingsRobolectricTestRunner.class) -@Config(shadows = SettingsShadowBluetoothDevice.class) +@Config(shadows = {SettingsShadowBluetoothDevice.class, ShadowBluetoothDevice.class}) public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsControllerTestBase { private BluetoothDetailsProfilesController mController; @@ -290,8 +290,7 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont @Test public void pbapProfileStartsEnabled() { setupDevice(makeDefaultDeviceConfig()); - when(mCachedDevice.getPhonebookPermissionChoice()) - .thenReturn(CachedBluetoothDevice.ACCESS_ALLOWED); + mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED); PbapServerProfile psp = mock(PbapServerProfile.class); when(psp.getNameResource(mDevice)).thenReturn(R.string.bluetooth_profile_pbap); when(psp.toString()).thenReturn(PbapServerProfile.NAME); @@ -306,14 +305,14 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont pref.performClick(); assertThat(mProfiles.getPreferenceCount()).isEqualTo(1); - verify(mCachedDevice).setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_REJECTED); + assertThat(mDevice.getPhonebookAccessPermission()) + .isEqualTo(BluetoothDevice.ACCESS_REJECTED); } @Test public void pbapProfileStartsDisabled() { setupDevice(makeDefaultDeviceConfig()); - when(mCachedDevice.getPhonebookPermissionChoice()) - .thenReturn(CachedBluetoothDevice.ACCESS_REJECTED); + mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED); PbapServerProfile psp = mock(PbapServerProfile.class); when(psp.getNameResource(mDevice)).thenReturn(R.string.bluetooth_profile_pbap); when(psp.toString()).thenReturn(PbapServerProfile.NAME); @@ -328,7 +327,8 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont pref.performClick(); assertThat(mProfiles.getPreferenceCount()).isEqualTo(1); - verify(mCachedDevice).setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED); + assertThat(mDevice.getPhonebookAccessPermission()) + .isEqualTo(BluetoothDevice.ACCESS_ALLOWED); } @Test @@ -338,8 +338,7 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont when(mapProfile.getNameResource(mDevice)).thenReturn(R.string.bluetooth_profile_map); when(mProfileManager.getMapProfile()).thenReturn(mapProfile); when(mProfileManager.getProfileByName(eq(mapProfile.toString()))).thenReturn(mapProfile); - when(mCachedDevice.getMessagePermissionChoice()) - .thenReturn(CachedBluetoothDevice.ACCESS_REJECTED); + mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_REJECTED); showScreen(mController); List switches = getProfileSwitches(false); assertThat(switches.size()).isEqualTo(1); @@ -349,7 +348,7 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont pref.performClick(); assertThat(mProfiles.getPreferenceCount()).isEqualTo(1); - verify(mCachedDevice).setMessagePermissionChoice(BluetoothDevice.ACCESS_ALLOWED); + assertThat(mDevice.getMessageAccessPermission()).isEqualTo(BluetoothDevice.ACCESS_ALLOWED); } private A2dpProfile addMockA2dpProfile(boolean preferred, boolean supportsHighQualityAudio, diff --git a/tests/robotests/src/com/android/settings/development/HighFrequencyPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/HighFrequencyPreferenceControllerTest.java new file mode 100644 index 00000000000..cf8babb5114 --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/HighFrequencyPreferenceControllerTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2018 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.development; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +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 static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.IBinder; +import android.os.RemoteException; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowParcel; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreference; + +@RunWith(SettingsRobolectricTestRunner.class) +public class HighFrequencyPreferenceControllerTest { + + private Context mContext; + private SwitchPreference mPreference; + + @Mock + private PreferenceScreen mScreen; + @Mock + private IBinder mSurfaceFlingerBinder; + + private HighFrequencyDisplayPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mPreference = new SwitchPreference(mContext); + mController = spy(new HighFrequencyDisplayPreferenceController(mContext)); + ReflectionHelpers.setField(mController, "mSurfaceFlingerBinder", mSurfaceFlingerBinder); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); + mController.displayPreference(mScreen); + } + + @Test + public void onPreferenceChange_settingToggledOn_shouldWriteTrueToHighFrequencySetting() { + mController.onPreferenceChange(mPreference, true /* new value */); + + verify(mController).writeHighFrequencyDisplaySetting(true); + } + + @Test + public void onPreferenceChange_settingToggledOff_shouldWriteFalseToHighFrequencySetting() { + mController.onPreferenceChange(mPreference, false /* new value */); + + verify(mController).writeHighFrequencyDisplaySetting(false); + } + + @Test + public void updateState_settingEnabled_shouldCheckPreference() throws RemoteException { + mController.writeHighFrequencyDisplaySetting(true); + mController.updateState(mPreference); + + verify(mController).readHighFrequencyDisplaySetting(); + } + + @Test + public void updateState_settingDisabled_shouldUnCheckPreference() throws RemoteException { + mController.writeHighFrequencyDisplaySetting(true); + mController.updateState(mPreference); + + verify(mController).readHighFrequencyDisplaySetting(); + } + + @Test + public void onDeveloperOptionsSwitchDisabled_preferenceChecked_shouldTurnOffPreference() { + mController.onDeveloperOptionsSwitchDisabled(); + + verify(mController).writeHighFrequencyDisplaySetting(false); + } + + @Test + public void onDeveloperOptionsSwitchDisabled_preferenceUnchecked_shouldNotTurnOffPreference() { + mController.onDeveloperOptionsSwitchDisabled(); + + verify(mController).writeHighFrequencyDisplaySetting(false); + } +} diff --git a/tests/robotests/src/com/android/settings/homepage/conditional/BackgroundDataConditionControllerTest.java b/tests/robotests/src/com/android/settings/homepage/conditional/BackgroundDataConditionControllerTest.java index 4ad7e6dee7b..e3db59b0853 100644 --- a/tests/robotests/src/com/android/settings/homepage/conditional/BackgroundDataConditionControllerTest.java +++ b/tests/robotests/src/com/android/settings/homepage/conditional/BackgroundDataConditionControllerTest.java @@ -22,28 +22,37 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.content.Intent; +import android.net.NetworkPolicyManager; import com.android.settings.Settings; -import com.android.settings.homepage.conditional.BackgroundDataConditionController; import com.android.settings.testutils.SettingsRobolectricTestRunner; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; +import org.robolectric.shadows.ShadowApplication; @RunWith(SettingsRobolectricTestRunner.class) public class BackgroundDataConditionControllerTest { + + @Mock + private ConditionManager mConditionManager; + @Mock + private NetworkPolicyManager mNetworkPolicyManager; private Context mContext; private BackgroundDataConditionController mController; @Before public void setUp() { MockitoAnnotations.initMocks(this); + ShadowApplication.getInstance().setSystemService(Context.NETWORK_POLICY_SERVICE, + mNetworkPolicyManager); mContext = spy(RuntimeEnvironment.application); - mController = new BackgroundDataConditionController(mContext); + mController = new BackgroundDataConditionController(mContext, mConditionManager); } @Test @@ -56,4 +65,10 @@ public class BackgroundDataConditionControllerTest { assertThat(intent.getComponent().getClassName()).isEqualTo( Settings.DataUsageSummaryActivity.class.getName()); } + + @Test + public void onActionClick_shouldRefreshCondition() { + mController.onActionClick(); + verify(mConditionManager).onConditionChanged(); + } } diff --git a/tests/robotests/src/com/android/settings/homepage/conditional/NightDisplayConditionControllerTest.java b/tests/robotests/src/com/android/settings/homepage/conditional/NightDisplayConditionControllerTest.java new file mode 100644 index 00000000000..130df90c696 --- /dev/null +++ b/tests/robotests/src/com/android/settings/homepage/conditional/NightDisplayConditionControllerTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018 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.homepage.conditional; + +import static org.mockito.Mockito.verify; + +import android.content.Context; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; + +@RunWith(SettingsRobolectricTestRunner.class) +public class NightDisplayConditionControllerTest { + + @Mock + private ConditionManager mConditionManager; + + private Context mContext; + private NightDisplayConditionController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mController = new NightDisplayConditionController(mContext, mConditionManager); + } + + @Test + public void onActivated_shouldUpdateCondition() { + mController.onActivated(true); + + verify(mConditionManager).onConditionChanged(); + } +} diff --git a/tests/robotests/src/com/android/settings/homepage/conditional/WorkModeConditionControllerTest.java b/tests/robotests/src/com/android/settings/homepage/conditional/WorkModeConditionControllerTest.java index 52c9ffedbf0..c993e68923d 100644 --- a/tests/robotests/src/com/android/settings/homepage/conditional/WorkModeConditionControllerTest.java +++ b/tests/robotests/src/com/android/settings/homepage/conditional/WorkModeConditionControllerTest.java @@ -24,24 +24,28 @@ import android.content.ComponentName; import android.content.Context; import com.android.settings.Settings; -import com.android.settings.homepage.conditional.WorkModeConditionController; import com.android.settings.testutils.SettingsRobolectricTestRunner; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; @RunWith(SettingsRobolectricTestRunner.class) public class WorkModeConditionControllerTest { + @Mock + private ConditionManager mConditionManager; private Context mContext; private WorkModeConditionController mController; @Before public void setUp() { + MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); - mController = new WorkModeConditionController(mContext); + mController = new WorkModeConditionController(mContext, mConditionManager); } @Test diff --git a/tests/robotests/src/com/android/settings/network/MobileNetworkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/MobileNetworkPreferenceControllerTest.java index 2c15a6dd088..2941e902c6e 100644 --- a/tests/robotests/src/com/android/settings/network/MobileNetworkPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/network/MobileNetworkPreferenceControllerTest.java @@ -15,9 +15,11 @@ */ package com.android.settings.network; -import static androidx.lifecycle.Lifecycle.Event.ON_START; -import static androidx.lifecycle.Lifecycle.Event.ON_STOP; +import static com.android.settings.network.MobileNetworkPreferenceController.MOBILE_NETWORK_CLASS; +import static com.android.settings.network.MobileNetworkPreferenceController.MOBILE_NETWORK_PACKAGE; + import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -25,14 +27,21 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.shadow.api.Shadow.extract; +import static androidx.lifecycle.Lifecycle.Event.ON_START; +import static androidx.lifecycle.Lifecycle.Event.ON_STOP; + +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.net.ConnectivityManager; import android.os.UserManager; import android.provider.Settings; import android.provider.Settings.Global; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; +import android.util.FeatureFlagUtils; +import com.android.settings.core.FeatureFlags; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.ShadowConnectivityManager; import com.android.settings.testutils.shadow.ShadowUserManager; @@ -43,6 +52,7 @@ import com.android.settingslib.core.lifecycle.Lifecycle; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; @@ -68,6 +78,7 @@ public class MobileNetworkPreferenceControllerTest { private Lifecycle mLifecycle; private LifecycleOwner mLifecycleOwner; private MobileNetworkPreferenceController mController; + private Preference mPreference; @Before public void setUp() { @@ -76,6 +87,8 @@ public class MobileNetworkPreferenceControllerTest { mLifecycleOwner = () -> mLifecycle; mLifecycle = new Lifecycle(mLifecycleOwner); when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager); + mPreference = new Preference(mContext); + mPreference.setKey(MobileNetworkPreferenceController.KEY_MOBILE_NETWORK_SETTINGS); } @Test @@ -173,4 +186,18 @@ public class MobileNetworkPreferenceControllerTest { mController.updateState(mPreference); assertThat(mPreference.isEnabled()).isFalse(); } + + @Test + public void handlePreferenceTreeClick_mobileFeatureDisabled_sendIntent() { + mController = new MobileNetworkPreferenceController(mContext); + FeatureFlagUtils.setEnabled(mContext, FeatureFlags.MOBILE_NETWORK_V2, false); + ArgumentCaptor argument = ArgumentCaptor.forClass(Intent.class); + + mController.handlePreferenceTreeClick(mPreference); + + verify(mContext).startActivity(argument.capture()); + final ComponentName componentName = argument.getValue().getComponent(); + assertThat(componentName.getPackageName()).isEqualTo(MOBILE_NETWORK_PACKAGE); + assertThat(componentName.getClassName()).isEqualTo(MOBILE_NETWORK_CLASS); + } } diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowApplicationsState.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowApplicationsState.java new file mode 100644 index 00000000000..3ee4fcb1647 --- /dev/null +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowApplicationsState.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018 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.testutils.shadow; + +import android.os.Looper; + +import com.android.settingslib.applications.ApplicationsState; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@Implements(ApplicationsState.class) +public class ShadowApplicationsState { + @Implementation + public Looper getBackgroundLooper() { + return Looper.getMainLooper(); + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowBluetoothDevice.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowBluetoothDevice.java new file mode 100644 index 00000000000..4cc77c3b072 --- /dev/null +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowBluetoothDevice.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2018 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.testutils.shadow; + +import android.bluetooth.BluetoothDevice; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@Implements(value = BluetoothDevice.class, inheritImplementationMethods = true) +public class ShadowBluetoothDevice extends org.robolectric.shadows.ShadowBluetoothDevice { + + private int mMessageAccessPermission = BluetoothDevice.ACCESS_UNKNOWN; + private int mPhonebookAccessPermission = BluetoothDevice.ACCESS_UNKNOWN; + private int mSimAccessPermission = BluetoothDevice.ACCESS_UNKNOWN; + + @Implementation + public void setMessageAccessPermission(int value) { + mMessageAccessPermission = value; + } + + @Implementation + public int getMessageAccessPermission() { + return mMessageAccessPermission; + } + + @Implementation + public void setPhonebookAccessPermission(int value) { + mPhonebookAccessPermission = value; + } + + @Implementation + public int getPhonebookAccessPermission() { + return mPhonebookAccessPermission; + } + + @Implementation + public void setSimAccessPermission(int value) { + mSimAccessPermission = value; + } + + @Implementation + public int getSimAccessPermission() { + return mSimAccessPermission; + } +} diff --git a/tests/robotests/src/com/android/settings/widget/ValidatedEditTextPreferenceTest.java b/tests/robotests/src/com/android/settings/widget/ValidatedEditTextPreferenceTest.java index 5b332825e3e..e5cb12d4405 100644 --- a/tests/robotests/src/com/android/settings/widget/ValidatedEditTextPreferenceTest.java +++ b/tests/robotests/src/com/android/settings/widget/ValidatedEditTextPreferenceTest.java @@ -131,4 +131,16 @@ public class ValidatedEditTextPreferenceTest { & (InputType.TYPE_TEXT_VARIATION_PASSWORD | InputType.TYPE_CLASS_TEXT)) .isNotEqualTo(0); } + + @Test + public void bindViewHolder_isNotPassword_shouldNotAutoCorrectText() { + final TextView textView = spy(new TextView(RuntimeEnvironment.application)); + when(mViewHolder.findViewById(android.R.id.summary)).thenReturn(textView); + + mPreference.setIsSummaryPassword(false); + mPreference.onBindViewHolder(mViewHolder); + + assertThat(textView.getInputType()).isEqualTo( + InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_CLASS_TEXT); + } }