diff --git a/res/drawable/ic_settings_sim.xml b/res/drawable/ic_settings_sim.xml index ca548cfefad..d083c9de6cf 100644 --- a/res/drawable/ic_settings_sim.xml +++ b/res/drawable/ic_settings_sim.xml @@ -15,7 +15,14 @@ limitations under the License. --> - - + + + + diff --git a/res/layout/choose_lock_pattern_common.xml b/res/layout/choose_lock_pattern_common.xml index 65135ddc90c..949d1303293 100644 --- a/res/layout/choose_lock_pattern_common.xml +++ b/res/layout/choose_lock_pattern_common.xml @@ -24,7 +24,7 @@ android:icon="@drawable/ic_lock" android:layout="@layout/suw_glif_blank_template" settings:suwFooter="@layout/choose_lock_pattern_common_footer" - settings:suwHeaderText="@string/lockpassword_choose_your_pattern_header"> + settings:suwHeaderText="@string/lockpassword_choose_your_screen_lock_header"> fragment, int titleRes, diff --git a/src/com/android/settings/applications/PictureInPictureSettings.java b/src/com/android/settings/applications/PictureInPictureSettings.java index d8e0b2bf8fc..5569a4ef785 100644 --- a/src/com/android/settings/applications/PictureInPictureSettings.java +++ b/src/com/android/settings/applications/PictureInPictureSettings.java @@ -22,14 +22,16 @@ import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; -import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.os.Bundle; import android.os.UserHandle; +import android.os.UserManager; import android.support.v7.preference.Preference; import android.support.v7.preference.Preference.OnPreferenceClickListener; import android.support.v7.preference.PreferenceScreen; -import android.util.ArrayMap; +import android.util.IconDrawableFactory; +import android.util.Pair; import android.view.View; import com.android.internal.annotations.VisibleForTesting; @@ -37,9 +39,13 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; import com.android.settings.notification.EmptyTextSettings; import com.android.settings.wrapper.ActivityInfoWrapper; +import com.android.settings.wrapper.UserManagerWrapper; +import com.android.settingslib.wrapper.PackageManagerWrapper; +import java.text.Collator; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; public class PictureInPictureSettings extends EmptyTextSettings { @@ -51,8 +57,38 @@ public class PictureInPictureSettings extends EmptyTextSettings { IGNORE_PACKAGE_LIST.add("com.android.systemui"); } + /** + * Comparator by name, then user id. + * {@see PackageItemInfo#DisplayNameComparator} + */ + static class AppComparator implements Comparator> { + + private final Collator mCollator = Collator.getInstance(); + private final PackageManager mPm; + + public AppComparator(PackageManager pm) { + mPm = pm; + } + + public final int compare(Pair a, + Pair b) { + CharSequence sa = a.first.loadLabel(mPm); + if (sa == null) sa = a.first.name; + CharSequence sb = b.first.loadLabel(mPm); + if (sb == null) sb = b.first.name; + int nameCmp = mCollator.compare(sa.toString(), sb.toString()); + if (nameCmp != 0) { + return nameCmp; + } else { + return a.second - b.second; + } + } + } + private Context mContext; - private PackageManager mPackageManager; + private PackageManagerWrapper mPackageManager; + private UserManagerWrapper mUserManager; + private IconDrawableFactory mIconDrawableFactory; /** * @return true if the package has any activities that declare that they support @@ -94,12 +130,23 @@ public class PictureInPictureSettings extends EmptyTextSettings { return false; } + public PictureInPictureSettings() { + // Do nothing + } + + public PictureInPictureSettings(PackageManagerWrapper pm, UserManagerWrapper um) { + mPackageManager = pm; + mUserManager = um; + } + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); mContext = getActivity(); - mPackageManager = mContext.getPackageManager(); + mPackageManager = new PackageManagerWrapper(mContext.getPackageManager()); + mUserManager = new UserManagerWrapper(mContext.getSystemService(UserManager.class)); + mIconDrawableFactory = IconDrawableFactory.newInstance(mContext); setPreferenceScreen(getPreferenceManager().createPreferenceScreen(mContext)); } @@ -111,33 +158,25 @@ public class PictureInPictureSettings extends EmptyTextSettings { final PreferenceScreen screen = getPreferenceScreen(); screen.removeAll(); - // Fetch the set of applications which have at least one activity that declare that they - // support picture-in-picture - final ArrayMap packageToState = new ArrayMap<>(); - final ArrayList pipApps = new ArrayList<>(); - final List installedPackages = mPackageManager.getInstalledPackagesAsUser( - GET_ACTIVITIES, UserHandle.myUserId()); - for (PackageInfo packageInfo : installedPackages) { - if (checkPackageHasPictureInPictureActivities(packageInfo.packageName, - packageInfo.activities)) { - final String packageName = packageInfo.applicationInfo.packageName; - final boolean state = PictureInPictureDetails.getEnterPipStateForPackage( - mContext, packageInfo.applicationInfo.uid, packageName); - pipApps.add(packageInfo.applicationInfo); - packageToState.put(packageName, state); - } - } - Collections.sort(pipApps, new PackageItemInfo.DisplayNameComparator(mPackageManager)); + // Fetch the set of applications for each profile which have at least one activity that + // declare that they support picture-in-picture + final PackageManager pm = mPackageManager.getPackageManager(); + final ArrayList> pipApps = + collectPipApps(UserHandle.myUserId()); + Collections.sort(pipApps, new AppComparator(pm)); // Rebuild the list of prefs final Context prefContext = getPrefContext(); - for (final ApplicationInfo appInfo : pipApps) { + for (final Pair appData : pipApps) { + final ApplicationInfo appInfo = appData.first; + final int userId = appData.second; + final UserHandle user = UserHandle.of(userId); final String packageName = appInfo.packageName; - final CharSequence label = appInfo.loadLabel(mPackageManager); + final CharSequence label = appInfo.loadLabel(pm); final Preference pref = new Preference(prefContext); - pref.setIcon(appInfo.loadIcon(mPackageManager)); - pref.setTitle(label); + pref.setIcon(mIconDrawableFactory.getBadgedIcon(appInfo, userId)); + pref.setTitle(pm.getUserBadgedLabel(label, user)); pref.setSummary(PictureInPictureDetails.getPreferenceSummary(prefContext, appInfo.uid, packageName)); pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { @@ -163,4 +202,28 @@ public class PictureInPictureSettings extends EmptyTextSettings { public int getMetricsCategory() { return MetricsEvent.SETTINGS_MANAGE_PICTURE_IN_PICTURE; } + + /** + * @return the list of applications for the given user and all their profiles that have + * activities which support PiP. + */ + ArrayList> collectPipApps(int userId) { + final ArrayList> pipApps = new ArrayList<>(); + final ArrayList userIds = new ArrayList<>(); + for (UserInfo user : mUserManager.getProfiles(userId)) { + userIds.add(user.id); + } + + for (int id : userIds) { + final List installedPackages = mPackageManager.getInstalledPackagesAsUser( + GET_ACTIVITIES, id); + for (PackageInfo packageInfo : installedPackages) { + if (checkPackageHasPictureInPictureActivities(packageInfo.packageName, + packageInfo.activities)) { + pipApps.add(new Pair<>(packageInfo.applicationInfo, id)); + } + } + } + return pipApps; + } } diff --git a/src/com/android/settings/applications/UsageAccessDetails.java b/src/com/android/settings/applications/UsageAccessDetails.java index e40ae37c23f..253ddfdbbba 100644 --- a/src/com/android/settings/applications/UsageAccessDetails.java +++ b/src/com/android/settings/applications/UsageAccessDetails.java @@ -137,6 +137,9 @@ public class UsageAccessDetails extends AppInfoWithHeader implements OnPreferenc @Override protected boolean refreshUi() { + if (mPackageInfo == null) { + return false; + } mUsageState = mUsageBridge.getUsageInfo(mPackageName, mPackageInfo.applicationInfo.uid); diff --git a/src/com/android/settings/bluetooth/BluetoothSummaryUpdater.java b/src/com/android/settings/bluetooth/BluetoothSummaryUpdater.java index 7d2cc181897..662cd701de2 100644 --- a/src/com/android/settings/bluetooth/BluetoothSummaryUpdater.java +++ b/src/com/android/settings/bluetooth/BluetoothSummaryUpdater.java @@ -42,31 +42,21 @@ public final class BluetoothSummaryUpdater extends SummaryUpdater implements Blu private final LocalBluetoothManager mBluetoothManager; private final LocalBluetoothAdapter mBluetoothAdapter; - private boolean mEnabled; - private int mConnectionState; - public BluetoothSummaryUpdater(Context context, OnSummaryChangeListener listener, LocalBluetoothManager bluetoothManager) { super(context, listener); mBluetoothManager = bluetoothManager; mBluetoothAdapter = mBluetoothManager != null - ? mBluetoothManager.getBluetoothAdapter() : null; + ? mBluetoothManager.getBluetoothAdapter() : null; } @Override public void onBluetoothStateChanged(int bluetoothState) { - mEnabled = bluetoothState == BluetoothAdapter.STATE_ON - || bluetoothState == BluetoothAdapter.STATE_TURNING_ON; - if (!mEnabled) { - mConnectionState = BluetoothAdapter.STATE_DISCONNECTED; - } notifyChangeIfNeeded(); } @Override public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { - mConnectionState = state; - updateConnected(); notifyChangeIfNeeded(); } @@ -92,8 +82,6 @@ public final class BluetoothSummaryUpdater extends SummaryUpdater implements Blu return; } if (listening) { - mEnabled = mBluetoothAdapter.isEnabled(); - mConnectionState = mBluetoothAdapter.getConnectionState(); notifyChangeIfNeeded(); mBluetoothManager.getEventManager().registerCallback(this); } else { @@ -103,10 +91,10 @@ public final class BluetoothSummaryUpdater extends SummaryUpdater implements Blu @Override public String getSummary() { - if (!mEnabled) { + if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) { return mContext.getString(R.string.bluetooth_disabled); } - switch (mConnectionState) { + switch (mBluetoothAdapter.getConnectionState()) { case BluetoothAdapter.STATE_CONNECTED: return getConnectedDeviceSummary(); case BluetoothAdapter.STATE_CONNECTING: @@ -118,50 +106,17 @@ public final class BluetoothSummaryUpdater extends SummaryUpdater implements Blu } } - private void updateConnected() { - if (mBluetoothAdapter == null) { - return; - } - // Make sure our connection state is up to date. - int state = mBluetoothAdapter.getConnectionState(); - if (state != mConnectionState) { - mConnectionState = state; - return; - } - final Collection devices = getDevices(); - if (devices == null) { - mConnectionState = BluetoothAdapter.STATE_DISCONNECTED; - return; - } - if (mConnectionState == BluetoothAdapter.STATE_CONNECTED) { - CachedBluetoothDevice connectedDevice = null; - for (CachedBluetoothDevice device : devices) { - if (device.isConnected()) { - connectedDevice = device; - break; - } - } - if (connectedDevice == null) { - // If somehow we think we are connected, but have no connected devices, we - // aren't connected. - mConnectionState = BluetoothAdapter.STATE_DISCONNECTED; - } - } - } - - private Collection getDevices() { - return mBluetoothManager != null - ? mBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy() - : null; - } - @VisibleForTesting String getConnectedDeviceSummary() { String deviceName = null; int count = 0; final Set devices = mBluetoothAdapter.getBondedDevices(); - if (devices == null || devices.isEmpty()) { - return null; + if (devices == null) { + Log.e(TAG, "getConnectedDeviceSummary, bonded devices are null"); + return mContext.getString(R.string.bluetooth_disabled); + } else if (devices.isEmpty()) { + Log.e(TAG, "getConnectedDeviceSummary, no bonded devices"); + return mContext.getString(R.string.disconnected); } for (BluetoothDevice device : devices) { if (device.isConnected()) { @@ -173,12 +128,13 @@ public final class BluetoothSummaryUpdater extends SummaryUpdater implements Blu } } if (deviceName == null) { - Log.w(TAG, "getConnectedDeviceSummary, deviceName is null, numBondedDevices=" + Log.e(TAG, "getConnectedDeviceSummary, deviceName is null, numBondedDevices=" + devices.size()); for (BluetoothDevice device : devices) { - Log.w(TAG, "getConnectedDeviceSummary, device=" + device.getName() + "[" + Log.e(TAG, "getConnectedDeviceSummary, device=" + device.getName() + "[" + device.getAddress() + "]" + ", isConnected=" + device.isConnected()); } + return mContext.getString(R.string.disconnected); } return count > 1 ? mContext.getString(R.string.bluetooth_connected_multiple_devices_summary) : mContext.getString(R.string.bluetooth_connected_summary, deviceName); diff --git a/src/com/android/settings/core/InstrumentedPreferenceFragment.java b/src/com/android/settings/core/InstrumentedPreferenceFragment.java index bfb69e78a3b..a5d07156190 100644 --- a/src/com/android/settings/core/InstrumentedPreferenceFragment.java +++ b/src/com/android/settings/core/InstrumentedPreferenceFragment.java @@ -65,4 +65,8 @@ public abstract class InstrumentedPreferenceFragment extends ObservablePreferenc protected final Context getPrefContext() { return getPreferenceManager().getContext(); } + + protected final VisibilityLoggerMixin getVisibilityLogger() { + return mVisibilityLoggerMixin; + } } diff --git a/src/com/android/settings/core/instrumentation/EventLogWriter.java b/src/com/android/settings/core/instrumentation/EventLogWriter.java index e7628e8b6b7..3196f76b323 100644 --- a/src/com/android/settings/core/instrumentation/EventLogWriter.java +++ b/src/com/android/settings/core/instrumentation/EventLogWriter.java @@ -18,6 +18,7 @@ package com.android.settings.core.instrumentation; import android.content.Context; import android.metrics.LogMaker; +import android.util.Log; import android.util.Pair; import com.android.internal.logging.MetricsLogger; @@ -28,6 +29,8 @@ import com.android.internal.logging.nano.MetricsProto; */ public class EventLogWriter implements LogWriter { + private final MetricsLogger mMetricsLogger = new MetricsLogger(); + public void visible(Context context, int source, int category) { final LogMaker logMaker = new LogMaker(category) .setType(MetricsProto.MetricsEvent.TYPE_OPEN) @@ -39,6 +42,24 @@ public class EventLogWriter implements LogWriter { MetricsLogger.hidden(context, category); } + public void action(int category, int value, Pair... taggedData) { + if (taggedData == null || taggedData.length == 0) { + mMetricsLogger.action(category, value); + } else { + final LogMaker logMaker = new LogMaker(category) + .setType(MetricsProto.MetricsEvent.TYPE_ACTION) + .setSubtype(value); + for (Pair pair : taggedData) { + logMaker.addTaggedData(pair.first, pair.second); + } + mMetricsLogger.write(logMaker); + } + } + + public void action(int category, boolean value, Pair... taggedData) { + action(category, value ? 1 : 0, taggedData); + } + public void action(Context context, int category, Pair... taggedData) { action(context, category, "", taggedData); } @@ -52,12 +73,16 @@ public class EventLogWriter implements LogWriter { MetricsLogger.action(logMaker); } + /** @deprecated use {@link #action(int, int, Pair[])} */ + @Deprecated public void action(Context context, int category, int value) { - MetricsLogger.action(context, category, Integer.toString(value)); + MetricsLogger.action(context, category, value); } + /** @deprecated use {@link #action(int, boolean, Pair[])} */ + @Deprecated public void action(Context context, int category, boolean value) { - MetricsLogger.action(context, category, Boolean.toString(value)); + MetricsLogger.action(context, category, value); } public void action(Context context, int category, String pkg, diff --git a/src/com/android/settings/core/instrumentation/LogWriter.java b/src/com/android/settings/core/instrumentation/LogWriter.java index 584217d85cb..062d46f759f 100644 --- a/src/com/android/settings/core/instrumentation/LogWriter.java +++ b/src/com/android/settings/core/instrumentation/LogWriter.java @@ -33,6 +33,16 @@ public interface LogWriter { */ void hidden(Context context, int category); + /** + * Logs a user action. + */ + void action(int category, int value, Pair... taggedData); + + /** + * Logs a user action. + */ + void action(int category, boolean value, Pair... taggedData); + /** * Logs an user action. */ @@ -45,12 +55,16 @@ public interface LogWriter { /** * Logs an user action. + * @deprecated use {@link #action(int, int, Pair[])} */ + @Deprecated void action(Context context, int category, int value); /** * Logs an user action. + * @deprecated use {@link #action(int, boolean, Pair[])} */ + @Deprecated void action(Context context, int category, boolean value); /** diff --git a/src/com/android/settings/core/instrumentation/MetricsFeatureProvider.java b/src/com/android/settings/core/instrumentation/MetricsFeatureProvider.java index afdec558556..532ec66316f 100644 --- a/src/com/android/settings/core/instrumentation/MetricsFeatureProvider.java +++ b/src/com/android/settings/core/instrumentation/MetricsFeatureProvider.java @@ -21,7 +21,7 @@ import android.content.Intent; import android.text.TextUtils; import android.util.Pair; -import com.android.internal.logging.nano.MetricsProto; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import java.util.ArrayList; import java.util.List; @@ -60,18 +60,44 @@ public class MetricsFeatureProvider { } } + /** + * Logs a user action. Includes the elapsed time since the containing + * fragment has been visible. + */ + public void action(VisibilityLoggerMixin visibilityLogger, int category, int value) { + for (LogWriter writer : mLoggerWriters) { + writer.action(category, value, + sinceVisibleTaggedData(visibilityLogger.elapsedTimeSinceVisible())); + } + } + + /** + * Logs a user action. Includes the elapsed time since the containing + * fragment has been visible. + */ + public void action(VisibilityLoggerMixin visibilityLogger, int category, boolean value) { + for (LogWriter writer : mLoggerWriters) { + writer.action(category, value, + sinceVisibleTaggedData(visibilityLogger.elapsedTimeSinceVisible())); + } + } + public void action(Context context, int category, Pair... taggedData) { for (LogWriter writer : mLoggerWriters) { writer.action(context, category, taggedData); } } + /** @deprecated use {@link #action(VisibilityLoggerMixin, int, int)} */ + @Deprecated public void action(Context context, int category, int value) { for (LogWriter writer : mLoggerWriters) { writer.action(context, category, value); } } + /** @deprecated use {@link #action(VisibilityLoggerMixin, int, boolean)} */ + @Deprecated public void action(Context context, int category, boolean value) { for (LogWriter writer : mLoggerWriters) { writer.action(context, category, value); @@ -99,7 +125,7 @@ public class MetricsFeatureProvider { public int getMetricsCategory(Object object) { if (object == null || !(object instanceof Instrumentable)) { - return MetricsProto.MetricsEvent.VIEW_UNKNOWN; + return MetricsEvent.VIEW_UNKNOWN; } return ((Instrumentable) object).getMetricsCategory(); } @@ -116,15 +142,19 @@ public class MetricsFeatureProvider { // Not loggable return; } - action(context, MetricsProto.MetricsEvent.ACTION_SETTINGS_TILE_CLICK, action, - Pair.create(MetricsProto.MetricsEvent.FIELD_CONTEXT, sourceMetricsCategory)); + action(context, MetricsEvent.ACTION_SETTINGS_TILE_CLICK, action, + Pair.create(MetricsEvent.FIELD_CONTEXT, sourceMetricsCategory)); return; } else if (TextUtils.equals(cn.getPackageName(), context.getPackageName())) { // Going to a Setting internal page, skip click logging in favor of page's own // visibility logging. return; } - action(context, MetricsProto.MetricsEvent.ACTION_SETTINGS_TILE_CLICK, cn.flattenToString(), - Pair.create(MetricsProto.MetricsEvent.FIELD_CONTEXT, sourceMetricsCategory)); + action(context, MetricsEvent.ACTION_SETTINGS_TILE_CLICK, cn.flattenToString(), + Pair.create(MetricsEvent.FIELD_CONTEXT, sourceMetricsCategory)); + } + + private Pair sinceVisibleTaggedData(long timestamp) { + return Pair.create(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS, timestamp); } } diff --git a/src/com/android/settings/core/instrumentation/SettingSuggestionsLogWriter.java b/src/com/android/settings/core/instrumentation/SettingSuggestionsLogWriter.java index bbdf8c94983..697f0a3c902 100644 --- a/src/com/android/settings/core/instrumentation/SettingSuggestionsLogWriter.java +++ b/src/com/android/settings/core/instrumentation/SettingSuggestionsLogWriter.java @@ -45,6 +45,14 @@ public class SettingSuggestionsLogWriter implements LogWriter { public void actionWithSource(Context context, int source, int category) { } + @Override + public void action(int category, int value, Pair... taggedData) { + } + + @Override + public void action(int category, boolean value, Pair... taggedData) { + } + @Override public void action(Context context, int category, int value) { } diff --git a/src/com/android/settings/core/instrumentation/VisibilityLoggerMixin.java b/src/com/android/settings/core/instrumentation/VisibilityLoggerMixin.java index 8de35adfd79..2fe2a3beb1d 100644 --- a/src/com/android/settings/core/instrumentation/VisibilityLoggerMixin.java +++ b/src/com/android/settings/core/instrumentation/VisibilityLoggerMixin.java @@ -20,6 +20,7 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.os.SystemClock; import com.android.internal.logging.nano.MetricsProto; import com.android.settings.SettingsActivity; import com.android.settings.overlay.FeatureFactory; @@ -41,6 +42,7 @@ public class VisibilityLoggerMixin implements LifecycleObserver, OnResume, OnPau private MetricsFeatureProvider mMetricsFeature; private int mSourceMetricsCategory = MetricsProto.MetricsEvent.VIEW_UNKNOWN; + private long mVisibleTimestamp; public VisibilityLoggerMixin(int metricsCategory) { // MetricsFeature will be set during onAttach. @@ -59,6 +61,7 @@ public class VisibilityLoggerMixin implements LifecycleObserver, OnResume, OnPau @Override public void onResume() { + mVisibleTimestamp = SystemClock.elapsedRealtime(); if (mMetricsFeature != null && mMetricsCategory != METRICS_CATEGORY_UNKNOWN) { mMetricsFeature.visible(null /* context */, mSourceMetricsCategory, mMetricsCategory); } @@ -66,6 +69,7 @@ public class VisibilityLoggerMixin implements LifecycleObserver, OnResume, OnPau @Override public void onPause() { + mVisibleTimestamp = 0; if (mMetricsFeature != null && mMetricsCategory != METRICS_CATEGORY_UNKNOWN) { mMetricsFeature.hidden(null /* context */, mMetricsCategory); } @@ -85,4 +89,12 @@ public class VisibilityLoggerMixin implements LifecycleObserver, OnResume, OnPau mSourceMetricsCategory = intent.getIntExtra(SettingsActivity.EXTRA_SOURCE_METRICS_CATEGORY, MetricsProto.MetricsEvent.VIEW_UNKNOWN); } + + /** Returns elapsed time since onResume() */ + public long elapsedTimeSinceVisible() { + if (mVisibleTimestamp == 0) { + return 0; + } + return SystemClock.elapsedRealtime() - mVisibleTimestamp; + } } diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionAdapter.java b/src/com/android/settings/dashboard/suggestions/SuggestionAdapter.java index 94435bda2c6..38b9d287528 100644 --- a/src/com/android/settings/dashboard/suggestions/SuggestionAdapter.java +++ b/src/com/android/settings/dashboard/suggestions/SuggestionAdapter.java @@ -220,6 +220,9 @@ public class SuggestionAdapter extends RecyclerView.Adapter public Tile getSuggestion(int position) { final long itemId = getItemId(position); + if (mSuggestions == null) { + return null; + } for (Tile tile : mSuggestions) { if (Objects.hash(tile.title) == itemId) { return tile; @@ -230,6 +233,9 @@ public class SuggestionAdapter extends RecyclerView.Adapter public Suggestion getSuggestionsV2(int position) { final long itemId = getItemId(position); + if (mSuggestionsV2 == null) { + return null; + } for (Suggestion suggestion : mSuggestionsV2) { if (Objects.hash(suggestion.getId()) == itemId) { return suggestion; diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java index f85d95a57af..1c61d8eae63 100644 --- a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java +++ b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java @@ -194,8 +194,7 @@ public class SuggestionFeatureProviderImpl implements SuggestionFeatureProvider @VisibleForTesting boolean hasUsedNightDisplay(Context context) { final ContentResolver cr = context.getContentResolver(); - final long lastActivatedTimeMillis = Secure.getLong(cr, - Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, -1); - return lastActivatedTimeMillis > 0; + return Secure.getInt(cr, Secure.NIGHT_DISPLAY_AUTO_MODE, 0) != 0 + || Secure.getString(cr, Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME) != null; } } diff --git a/src/com/android/settings/development/BluetoothSnoopLogPreferenceController.java b/src/com/android/settings/development/BluetoothSnoopLogPreferenceController.java index 7734e938b29..44a2ecd19e4 100644 --- a/src/com/android/settings/development/BluetoothSnoopLogPreferenceController.java +++ b/src/com/android/settings/development/BluetoothSnoopLogPreferenceController.java @@ -69,12 +69,12 @@ public class BluetoothSnoopLogPreferenceController extends } @Override - public void onDeveloperOptionsEnabled() { + protected void onDeveloperOptionsSwitchEnabled() { mPreference.setEnabled(true); } @Override - public void onDeveloperOptionsDisabled() { + protected void onDeveloperOptionsSwitchDisabled() { SystemProperties.set(BLUETOOTH_BTSNOOP_ENABLE_PROPERTY, Boolean.toString(false)); mPreference.setChecked(false); mPreference.setEnabled(false); diff --git a/src/com/android/settings/development/ColorModePreference.java b/src/com/android/settings/development/ColorModePreference.java index e0b0837d800..20af2c62ac8 100644 --- a/src/com/android/settings/development/ColorModePreference.java +++ b/src/com/android/settings/development/ColorModePreference.java @@ -28,6 +28,7 @@ import android.view.Display; import com.android.settings.R; import java.util.ArrayList; +import java.util.List; public class ColorModePreference extends SwitchPreference implements DisplayListener { @@ -35,7 +36,28 @@ public class ColorModePreference extends SwitchPreference implements DisplayList private Display mDisplay; private int mCurrentIndex; - private ArrayList mDescriptions; + private List mDescriptions; + + public static List getColorModeDescriptions(Context context) { + + List colorModeDescriptions = new ArrayList<>(); + Resources resources = context.getResources(); + int[] colorModes = resources.getIntArray(R.array.color_mode_ids); + String[] titles = resources.getStringArray(R.array.color_mode_names); + String[] descriptions = resources.getStringArray(R.array.color_mode_descriptions); + // Map the resource information describing color modes. + for (int i = 0; i < colorModes.length; i++) { + if (colorModes[i] != -1 && i != 1 /* Skip Natural for now. */) { + ColorModeDescription desc = new ColorModeDescription(); + desc.colorMode = colorModes[i]; + desc.title = titles[i]; + desc.summary = descriptions[i]; + colorModeDescriptions.add(desc); + } + } + + return colorModeDescriptions; + } public ColorModePreference(Context context, AttributeSet attrs) { super(context, attrs); @@ -75,22 +97,7 @@ public class ColorModePreference extends SwitchPreference implements DisplayList public void updateCurrentAndSupported() { mDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY); - mDescriptions = new ArrayList<>(); - - Resources resources = getContext().getResources(); - int[] colorModes = resources.getIntArray(R.array.color_mode_ids); - String[] titles = resources.getStringArray(R.array.color_mode_names); - String[] descriptions = resources.getStringArray(R.array.color_mode_descriptions); - // Map the resource information describing color modes. - for (int i = 0; i < colorModes.length; i++) { - if (colorModes[i] != -1 && i != 1 /* Skip Natural for now. */) { - ColorModeDescription desc = new ColorModeDescription(); - desc.colorMode = colorModes[i]; - desc.title = titles[i]; - desc.summary = descriptions[i]; - mDescriptions.add(desc); - } - } + mDescriptions = getColorModeDescriptions(getContext()); int currentColorMode = mDisplay.getColorMode(); mCurrentIndex = -1; diff --git a/src/com/android/settings/development/DeveloperOptionsPreferenceController.java b/src/com/android/settings/development/DeveloperOptionsPreferenceController.java index a7d45ebba8e..2f1f2540ed0 100644 --- a/src/com/android/settings/development/DeveloperOptionsPreferenceController.java +++ b/src/com/android/settings/development/DeveloperOptionsPreferenceController.java @@ -17,6 +17,7 @@ package com.android.settings.development; import android.content.Context; +import android.content.Intent; import com.android.settings.core.PreferenceControllerMixin; import com.android.settingslib.core.AbstractPreferenceController; @@ -35,12 +36,47 @@ public abstract class DeveloperOptionsPreferenceController extends } /** - * Called when developer options is enabled + * Called when an activity returns to the DeveloperSettingsDashboardFragment. + * + * @param requestCode The integer request code originally supplied to + * startActivityForResult(), allowing you to identify who this + * result came from. + * @param resultCode The integer result code returned by the child activity + * through its setResult(). + * @param data An Intent, which can return result data to the caller + * (various data can be attached to Intent "extras"). + * @return true if the controller handled the activity result */ - public abstract void onDeveloperOptionsEnabled(); + public boolean onActivityResult(int requestCode, int resultCode, Intent data) { + return false; + } /** - *Called when developer options is disabled + * Called when developer options is enabled */ - public abstract void onDeveloperOptionsDisabled(); + public void onDeveloperOptionsEnabled() { + if (isAvailable()) { + onDeveloperOptionsSwitchEnabled(); + } + } + + /** + * Called when developer options is disabled + */ + public void onDeveloperOptionsDisabled() { + if (isAvailable()) { + onDeveloperOptionsSwitchDisabled(); + } + } + + /** + * Called when developer options is enabled and the preference is available + */ + protected abstract void onDeveloperOptionsSwitchEnabled(); + + /** + * Called when developer options is disabled and the preference is available + */ + protected abstract void onDeveloperOptionsSwitchDisabled(); + } diff --git a/src/com/android/settings/development/DevelopmentOptionsActivityRequestCodes.java b/src/com/android/settings/development/DevelopmentOptionsActivityRequestCodes.java new file mode 100644 index 00000000000..54d1fa39d23 --- /dev/null +++ b/src/com/android/settings/development/DevelopmentOptionsActivityRequestCodes.java @@ -0,0 +1,24 @@ +/* + * 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.development; + +/** + * Interface for storing Activity request codes in development options + */ +public interface DevelopmentOptionsActivityRequestCodes { + int REQUEST_CODE_ENABLE_OEM_UNLOCK = 0; +} diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index 8df25c227c3..d334bd05185 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -16,10 +16,13 @@ package com.android.settings.development; +import android.app.Activity; import android.content.Context; +import android.content.Intent; import android.os.Bundle; import android.os.UserManager; import android.provider.SearchIndexableResource; +import android.support.annotation.VisibleForTesting; import android.util.Log; import android.widget.Switch; @@ -40,7 +43,7 @@ import java.util.Arrays; import java.util.List; public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFragment - implements SwitchBar.OnSwitchChangeListener { + implements SwitchBar.OnSwitchChangeListener, OemUnlockDialogHost { private static final String TAG = "DevSettingsDashboard"; @@ -103,6 +106,33 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra } } + @Override + public void onOemUnlockDialogConfirmed() { + final OemUnlockPreferenceController controller = getDevelopmentOptionsController( + OemUnlockPreferenceController.class); + controller.onOemUnlockConfirmed(); + } + + @Override + public void onOemUnlockDialogDismissed() { + final OemUnlockPreferenceController controller = getDevelopmentOptionsController( + OemUnlockPreferenceController.class); + controller.onOemUnlockDismissed(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + for (AbstractPreferenceController controller : mPreferenceControllers) { + if (controller instanceof DeveloperOptionsPreferenceController) { + if (((DeveloperOptionsPreferenceController) controller).onActivityResult( + requestCode, resultCode, data)) { + return; + } + } + } + super.onActivityResult(requestCode, resultCode, data); + } + @Override protected String getLogTag() { return TAG; @@ -121,7 +151,8 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra @Override protected List getPreferenceControllers(Context context) { - mPreferenceControllers = buildPreferenceControllers(context, getLifecycle()); + mPreferenceControllers = buildPreferenceControllers(context, getActivity(), getLifecycle(), + this /* devOptionsDashboardFragment */); return mPreferenceControllers; } @@ -140,14 +171,92 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra } private static List buildPreferenceControllers(Context context, - Lifecycle lifecycle) { + Activity activity, Lifecycle lifecycle, DevelopmentSettingsDashboardFragment fragment) { final List controllers = new ArrayList<>(); + // take bug report + // desktop backup password controllers.add(new StayAwakePreferenceController(context, lifecycle)); + // hdcp checking controllers.add(new BluetoothSnoopLogPreferenceController(context)); - + controllers.add(new OemUnlockPreferenceController(context, activity, fragment)); + // running services + // convert to file encryption + controllers.add(new PictureColorModePreferenceController(context, lifecycle)); + // webview implementation + // cool color temperature + // automatic system updates + // system ui demo mode + // quick settings developer tiles + // usb debugging + // revoke usb debugging authorizations + // local terminal + // bug report shortcut + // select mock location app + // enable view attribute inspection + // select debug app + // wait for debugger + // verify apps over usb + // logger buffer sizes + // store logger data persistently on device + // telephony monitor + // camera laser sensor + // camera HAL HDR+ + // feature flags + // wireless display certification + // enable wi-fi verbose logging + // aggressive wifi to mobile handover + // always allow wifi roam scans + // mobile always active + // tethering hardware acceleration + // select usb configuration + // show bluetooth devices without names + // disable absolute volume + // enable in-band ringing + // bluetooth avrcp version + // bluetooth audio codec + // bluetooth audio sample rate + // bluetooth audio bits per sample + // bluetooth audio channel mode + // bluetooth audio ldac codec: playback quality + // show taps + // pointer location + // show surface updates + // show layout bounds + // force rtl layout direction + // window animation scale + // transition animation scale + // animator duration scale + // simulate secondary displays + // smallest width + // force gpu rendering + // show gpu view updates + // show hardware layers updates + // debug gpu overdraw + // debug non-rectangular clip operations + // force 4x msaa + // disable hw overlays + // simulate color space + // set gpu renderer + // disable usb audio routing + // strict mode enabled + // profile gpu rendering + // don't keep activities + // background process limit + // background check + // show all anrs + // show notification channel warnings + // inactive apps + // force allow apps on external + // force activities to be resizable + // reset shortcutmanager rate-limiting return controllers; } + @VisibleForTesting + T getDevelopmentOptionsController(Class clazz) { + return getPreferenceController(clazz); + } + /** * For Search. */ @@ -171,7 +280,8 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra @Override public List getPreferenceControllers(Context context) { - return buildPreferenceControllers(context, null /* lifecycle */); + return buildPreferenceControllers(context, null /* activity */, + null /* lifecycle */, null /* devOptionsDashboardFragment */); } }; } diff --git a/src/com/android/settings/development/EnableOemUnlockSettingWarningDialog.java b/src/com/android/settings/development/EnableOemUnlockSettingWarningDialog.java new file mode 100644 index 00000000000..2486ef53589 --- /dev/null +++ b/src/com/android/settings/development/EnableOemUnlockSettingWarningDialog.java @@ -0,0 +1,82 @@ +/* + * 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.development; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.Fragment; +import android.app.FragmentManager; +import android.content.DialogInterface; +import android.os.Bundle; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; +import com.android.settings.core.instrumentation.InstrumentedDialogFragment; + +public class EnableOemUnlockSettingWarningDialog extends InstrumentedDialogFragment implements + DialogInterface.OnClickListener, DialogInterface.OnDismissListener { + + public static final String TAG = "EnableOemUnlockDlg"; + + public static void show(Fragment host) { + final FragmentManager manager = host.getActivity().getFragmentManager(); + if (manager.findFragmentByTag(TAG) == null) { + final EnableOemUnlockSettingWarningDialog dialog = + new EnableOemUnlockSettingWarningDialog(); + dialog.setTargetFragment(host, 0 /* requestCode */); + dialog.show(manager, TAG); + } + } + + @Override + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.DIALOG_ENABLE_OEM_UNLOCKING; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.confirm_enable_oem_unlock_title) + .setMessage(R.string.confirm_enable_oem_unlock_text) + .setPositiveButton(R.string.enable_text, this /* onClickListener */) + .setNegativeButton(android.R.string.cancel, this /* onClickListener */) + .create(); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + final OemUnlockDialogHost host = (OemUnlockDialogHost) getTargetFragment(); + if (host == null) { + return; + } + if (which == DialogInterface.BUTTON_POSITIVE) { + host.onOemUnlockDialogConfirmed(); + } else { + host.onOemUnlockDialogDismissed(); + } + } + + @Override + public void onDismiss(DialogInterface dialog) { + super.onDismiss(dialog); + final OemUnlockDialogHost host = (OemUnlockDialogHost) getTargetFragment(); + if (host == null) { + return; + } + host.onOemUnlockDialogDismissed(); + } +} diff --git a/src/com/android/settings/development/OemUnlockDialogHost.java b/src/com/android/settings/development/OemUnlockDialogHost.java new file mode 100644 index 00000000000..c134e9c2ea2 --- /dev/null +++ b/src/com/android/settings/development/OemUnlockDialogHost.java @@ -0,0 +1,33 @@ +/* + * 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.development; + +/** + * Interface for OemUnlockDialogFragment callbacks. + */ +public interface OemUnlockDialogHost { + + /** + * Called when the user presses enable on the warning dialog. + */ + void onOemUnlockDialogConfirmed(); + + /** + * Called when the user dismisses or cancels the warning dialog. + */ + void onOemUnlockDialogDismissed(); +} diff --git a/src/com/android/settings/development/OemUnlockPreferenceController.java b/src/com/android/settings/development/OemUnlockPreferenceController.java new file mode 100644 index 00000000000..cb391a8e893 --- /dev/null +++ b/src/com/android/settings/development/OemUnlockPreferenceController.java @@ -0,0 +1,216 @@ +/* + * 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.development; + +import static com.android.settings.development.DevelopmentOptionsActivityRequestCodes + .REQUEST_CODE_ENABLE_OEM_UNLOCK; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.os.UserHandle; +import android.os.UserManager; +import android.service.oemlock.OemLockManager; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; +import android.telephony.TelephonyManager; + +import com.android.settings.R; +import com.android.settings.password.ChooseLockSettingsHelper; +import com.android.settingslib.RestrictedSwitchPreference; + +public class OemUnlockPreferenceController extends DeveloperOptionsPreferenceController implements + Preference.OnPreferenceChangeListener { + + private static final String PREFERENCE_KEY = "oem_unlock_enable"; + + private final OemLockManager mOemLockManager; + private final UserManager mUserManager; + private final TelephonyManager mTelephonyManager; + private final DevelopmentSettingsDashboardFragment mFragment; + private final ChooseLockSettingsHelper mChooseLockSettingsHelper; + private RestrictedSwitchPreference mPreference; + + public OemUnlockPreferenceController(Context context, Activity activity, + DevelopmentSettingsDashboardFragment fragment) { + super(context); + mOemLockManager = (OemLockManager) context.getSystemService(Context.OEM_LOCK_SERVICE); + mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); + mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + mFragment = fragment; + if (activity != null || mFragment != null) { + mChooseLockSettingsHelper = new ChooseLockSettingsHelper(activity, mFragment); + } else { + mChooseLockSettingsHelper = null; + } + } + + @Override + public boolean isAvailable() { + return mOemLockManager != null; + } + + @Override + public String getPreferenceKey() { + return PREFERENCE_KEY; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + mPreference = (RestrictedSwitchPreference) screen.findPreference(getPreferenceKey()); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + boolean isUnlocked = (Boolean) newValue; + if (isUnlocked) { + if (!showKeyguardConfirmation(mContext.getResources(), + REQUEST_CODE_ENABLE_OEM_UNLOCK)) { + confirmEnableOemUnlock(); + } + } else { + mOemLockManager.setOemUnlockAllowedByUser(false); + } + return true; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + mPreference.setChecked(mOemLockManager.isOemUnlockAllowed()); + updateOemUnlockSettingDescription(); + // Showing mEnableOemUnlock preference as device has persistent data block. + mPreference.setDisabledByAdmin(null); + mPreference.setEnabled(enableOemUnlockPreference()); + if (mPreference.isEnabled()) { + // Check restriction, disable mEnableOemUnlock and apply policy transparency. + mPreference.checkRestrictionAndSetDisabled(UserManager.DISALLOW_FACTORY_RESET); + } + } + + @Override + public boolean onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == REQUEST_CODE_ENABLE_OEM_UNLOCK) { + if (resultCode == Activity.RESULT_OK) { + if (mPreference.isChecked()) { + confirmEnableOemUnlock(); + } else { + mOemLockManager.setOemUnlockAllowedByUser(false); + } + } + return true; + } + return false; + } + + @Override + protected void onDeveloperOptionsSwitchEnabled() { + handleDeveloperOptionsToggled(); + } + + @Override + protected void onDeveloperOptionsSwitchDisabled() { + handleDeveloperOptionsToggled(); + } + + public void onOemUnlockConfirmed() { + mOemLockManager.setOemUnlockAllowedByUser(true); + } + + public void onOemUnlockDismissed() { + if (mPreference == null) { + return; + } + updateState(mPreference); + } + + private void handleDeveloperOptionsToggled() { + mPreference.setEnabled(enableOemUnlockPreference()); + if (mPreference.isEnabled()) { + // Check restriction, disable mEnableOemUnlock and apply policy transparency. + mPreference.checkRestrictionAndSetDisabled(UserManager.DISALLOW_FACTORY_RESET); + } + } + + private void updateOemUnlockSettingDescription() { + int oemUnlockSummary = R.string.oem_unlock_enable_summary; + if (isBootloaderUnlocked()) { + oemUnlockSummary = R.string.oem_unlock_enable_disabled_summary_bootloader_unlocked; + } else if (isSimLockedDevice()) { + oemUnlockSummary = R.string.oem_unlock_enable_disabled_summary_sim_locked_device; + } else if (!isOemUnlockAllowedByUserAndCarrier()) { + // If the device isn't SIM-locked but OEM unlock is disallowed by some party, this + // means either some other carrier restriction is in place or the device hasn't been + // able to confirm which restrictions (SIM-lock or otherwise) apply. + oemUnlockSummary = + R.string.oem_unlock_enable_disabled_summary_connectivity_or_locked; + } + mPreference.setSummary(mContext.getResources().getString(oemUnlockSummary)); + } + + /** Returns {@code true} if the device is SIM-locked. Otherwise, returns {@code false}. */ + private boolean isSimLockedDevice() { + int phoneCount = mTelephonyManager.getPhoneCount(); + for (int i = 0; i < phoneCount; i++) { + if (mTelephonyManager.getAllowedCarriers(i).size() > 0) { + return true; + } + } + return false; + } + + /** + * Returns {@code true} if the bootloader has been unlocked. Otherwise, returns {code false}. + */ + private boolean isBootloaderUnlocked() { + return mOemLockManager.isDeviceOemUnlocked(); + } + + private boolean enableOemUnlockPreference() { + return !isBootloaderUnlocked() && isOemUnlockAllowedByUserAndCarrier(); + } + + + @VisibleForTesting + boolean showKeyguardConfirmation(Resources resources, int requestCode) { + return mChooseLockSettingsHelper.launchConfirmationActivity( + requestCode, resources.getString(R.string.oem_unlock_enable)); + } + + @VisibleForTesting + void confirmEnableOemUnlock() { + EnableOemUnlockSettingWarningDialog.show(mFragment); + } + + /** + * Returns whether OEM unlock is allowed by the user and carrier. + * + * This does not take into account any restrictions imposed by the device policy. + */ + @VisibleForTesting + boolean isOemUnlockAllowedByUserAndCarrier() { + final UserHandle userHandle = UserHandle.of(UserHandle.myUserId()); + return mOemLockManager.isOemUnlockAllowedByCarrier() + && !mUserManager.hasBaseUserRestriction(UserManager.DISALLOW_FACTORY_RESET, + userHandle); + } + +} diff --git a/src/com/android/settings/development/PictureColorModePreferenceController.java b/src/com/android/settings/development/PictureColorModePreferenceController.java new file mode 100644 index 00000000000..fe4755ff409 --- /dev/null +++ b/src/com/android/settings/development/PictureColorModePreferenceController.java @@ -0,0 +1,99 @@ +/* + * 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.development; + +import android.content.Context; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnPause; +import com.android.settingslib.core.lifecycle.events.OnResume; + +public class PictureColorModePreferenceController extends + DeveloperOptionsPreferenceController implements + LifecycleObserver, OnResume, OnPause { + + private static final String KEY_COLOR_MODE = "picture_color_mode"; + + private ColorModePreference mPreference; + + public PictureColorModePreferenceController(Context context, Lifecycle lifecycle) { + super(context); + + if (lifecycle != null) { + lifecycle.addObserver(this); + } + } + + @Override + public boolean isAvailable() { + return getColorModeDescriptionsSize() > 1 && !isWideColorGamut(); + } + + @Override + public String getPreferenceKey() { + return KEY_COLOR_MODE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = (ColorModePreference) screen.findPreference(getPreferenceKey()); + if (mPreference != null) { + mPreference.updateCurrentAndSupported(); + } + } + + @Override + public void onResume() { + if (mPreference == null) { + return; + } + mPreference.startListening(); + mPreference.updateCurrentAndSupported(); + } + + @Override + public void onPause() { + if (mPreference == null) { + return; + } + mPreference.stopListening(); + } + + @Override + protected void onDeveloperOptionsSwitchEnabled() { + mPreference.setEnabled(true); + } + + @Override + protected void onDeveloperOptionsSwitchDisabled() { + mPreference.setEnabled(false); + } + + @VisibleForTesting + boolean isWideColorGamut() { + return mContext.getDisplay().isWideColorGamut(); + } + + @VisibleForTesting + int getColorModeDescriptionsSize() { + return ColorModePreference.getColorModeDescriptions(mContext).size(); + } +} diff --git a/src/com/android/settings/development/StayAwakePreferenceController.java b/src/com/android/settings/development/StayAwakePreferenceController.java index a590d7d66af..ecbb9d0b576 100644 --- a/src/com/android/settings/development/StayAwakePreferenceController.java +++ b/src/com/android/settings/development/StayAwakePreferenceController.java @@ -100,19 +100,6 @@ public class StayAwakePreferenceController extends DeveloperOptionsPreferenceCon mPreference.setChecked(stayAwakeMode != SETTING_VALUE_OFF); } - @Override - public void onDeveloperOptionsEnabled() { - mPreference.setEnabled(true); - } - - @Override - public void onDeveloperOptionsDisabled() { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.STAY_ON_WHILE_PLUGGED_IN, SETTING_VALUE_OFF); - mPreference.setChecked(false); - mPreference.setEnabled(false); - } - @Override public void onResume() { if (mPreference != null) { @@ -127,6 +114,19 @@ public class StayAwakePreferenceController extends DeveloperOptionsPreferenceCon } } + @Override + protected void onDeveloperOptionsSwitchEnabled() { + mPreference.setEnabled(true); + } + + @Override + protected void onDeveloperOptionsSwitchDisabled() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, SETTING_VALUE_OFF); + mPreference.setChecked(false); + mPreference.setEnabled(false); + } + @VisibleForTesting RestrictedLockUtils.EnforcedAdmin checkIfMaximumTimeToLockSetByAdmin() { // A DeviceAdmin has specified a maximum time until the device diff --git a/src/com/android/settings/display/NightDisplayPreference.java b/src/com/android/settings/display/NightDisplayPreference.java index 38b57a2460b..b966530d9f1 100644 --- a/src/com/android/settings/display/NightDisplayPreference.java +++ b/src/com/android/settings/display/NightDisplayPreference.java @@ -22,6 +22,7 @@ import com.android.internal.app.NightDisplayController; import com.android.settings.R; import java.text.DateFormat; +import java.time.LocalTime; import java.util.Calendar; import java.util.TimeZone; @@ -58,11 +59,11 @@ public class NightDisplayPreference extends SwitchPreference mController.setListener(null); } - private String getFormattedTimeString(NightDisplayController.LocalTime localTime) { + private String getFormattedTimeString(LocalTime localTime) { final Calendar c = Calendar.getInstance(); c.setTimeZone(mTimeFormatter.getTimeZone()); - c.set(Calendar.HOUR_OF_DAY, localTime.hourOfDay); - c.set(Calendar.MINUTE, localTime.minute); + c.set(Calendar.HOUR_OF_DAY, localTime.getHour()); + c.set(Calendar.MINUTE, localTime.getMinute()); c.set(Calendar.SECOND, 0); c.set(Calendar.MILLISECOND, 0); return mTimeFormatter.format(c.getTime()); @@ -116,12 +117,12 @@ public class NightDisplayPreference extends SwitchPreference } @Override - public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) { + public void onCustomStartTimeChanged(LocalTime startTime) { updateSummary(); } @Override - public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) { + public void onCustomEndTimeChanged(LocalTime endTime) { updateSummary(); } } diff --git a/src/com/android/settings/display/NightDisplaySettings.java b/src/com/android/settings/display/NightDisplaySettings.java index 23ddf078ac4..f9568fb6f50 100644 --- a/src/com/android/settings/display/NightDisplaySettings.java +++ b/src/com/android/settings/display/NightDisplaySettings.java @@ -32,6 +32,7 @@ import com.android.settings.widget.SeekBarPreference; import com.android.settings.SettingsPreferenceFragment; import java.text.DateFormat; +import java.time.LocalTime; import java.util.Calendar; import java.util.TimeZone; @@ -144,7 +145,7 @@ public class NightDisplaySettings extends SettingsPreferenceFragment @Override public Dialog onCreateDialog(final int dialogId) { if (dialogId == DIALOG_START_TIME || dialogId == DIALOG_END_TIME) { - final NightDisplayController.LocalTime initialTime; + final LocalTime initialTime; if (dialogId == DIALOG_START_TIME) { initialTime = mController.getCustomStartTime(); } else { @@ -156,15 +157,14 @@ public class NightDisplaySettings extends SettingsPreferenceFragment return new TimePickerDialog(context, new TimePickerDialog.OnTimeSetListener() { @Override public void onTimeSet(TimePicker view, int hourOfDay, int minute) { - final NightDisplayController.LocalTime time = - new NightDisplayController.LocalTime(hourOfDay, minute); + final LocalTime time = LocalTime.of(hourOfDay, minute); if (dialogId == DIALOG_START_TIME) { mController.setCustomStartTime(time); } else { mController.setCustomEndTime(time); } } - }, initialTime.hourOfDay, initialTime.minute, use24HourFormat); + }, initialTime.getHour(), initialTime.getMinute(), use24HourFormat); } return super.onCreateDialog(dialogId); } @@ -201,11 +201,11 @@ public class NightDisplaySettings extends SettingsPreferenceFragment mTemperaturePreference.setProgress(convertTemperature(colorTemperature)); } - private String getFormattedTimeString(NightDisplayController.LocalTime localTime) { + private String getFormattedTimeString(LocalTime localTime) { final Calendar c = Calendar.getInstance(); c.setTimeZone(mTimeFormatter.getTimeZone()); - c.set(Calendar.HOUR_OF_DAY, localTime.hourOfDay); - c.set(Calendar.MINUTE, localTime.minute); + c.set(Calendar.HOUR_OF_DAY, localTime.getHour()); + c.set(Calendar.MINUTE, localTime.getMinute()); c.set(Calendar.SECOND, 0); c.set(Calendar.MILLISECOND, 0); return mTimeFormatter.format(c.getTime()); @@ -221,12 +221,12 @@ public class NightDisplaySettings extends SettingsPreferenceFragment } @Override - public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) { + public void onCustomStartTimeChanged(LocalTime startTime) { mStartTimePreference.setSummary(getFormattedTimeString(startTime)); } @Override - public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) { + public void onCustomEndTimeChanged(LocalTime endTime) { mEndTimePreference.setSummary(getFormattedTimeString(endTime)); } diff --git a/src/com/android/settings/notification/ChannelNotificationSettings.java b/src/com/android/settings/notification/ChannelNotificationSettings.java index e9f8cafa8ba..5db256d3859 100644 --- a/src/com/android/settings/notification/ChannelNotificationSettings.java +++ b/src/com/android/settings/notification/ChannelNotificationSettings.java @@ -360,7 +360,9 @@ public class ChannelNotificationSettings extends NotificationSettingsBase { } void updateDependents(boolean banned) { + PreferenceGroup parent; if (mShowLegacyChannelConfig) { + parent = getPreferenceScreen(); setVisible(mImportanceToggle, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN)); } else { setVisible(mAdvanced, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN)); @@ -369,12 +371,13 @@ public class ChannelNotificationSettings extends NotificationSettingsBase { NotificationManager.IMPORTANCE_DEFAULT) && canPulseLight()); setVisible(mVibrate, checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT)); setVisible(mRingtone, checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT)); + parent = mAdvanced; } - setVisible(mAdvanced, mBadge, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN)); - setVisible(mAdvanced, mPriority, checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT) + setVisible(parent, mBadge, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN)); + setVisible(parent, mPriority, checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT) || (checkCanBeVisible(NotificationManager.IMPORTANCE_LOW) && mDndVisualEffectsSuppressed)); - setVisible(mAdvanced, mVisibilityOverride, isLockScreenSecure() + setVisible(parent, mVisibilityOverride, isLockScreenSecure() &&checkCanBeVisible(NotificationManager.IMPORTANCE_LOW)); setVisible(mBlockedDesc, mChannel.getImportance() == IMPORTANCE_NONE); if (mAppLink != null) { diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java index b47391d0dd7..b143f5854de 100644 --- a/src/com/android/settings/wifi/WifiSettings.java +++ b/src/com/android/settings/wifi/WifiSettings.java @@ -1070,7 +1070,7 @@ public class WifiSettings extends RestrictedSettingsFragment protected void connect(final WifiConfiguration config, boolean isSavedNetwork) { // Log subtype if configuration is a saved network. - mMetricsFeatureProvider.action(getActivity(), MetricsEvent.ACTION_WIFI_CONNECT, + mMetricsFeatureProvider.action(getVisibilityLogger(), MetricsEvent.ACTION_WIFI_CONNECT, isSavedNetwork); mWifiManager.connect(config, mConnectListener); mClickedConnect = true; diff --git a/src/com/android/settings/wrapper/UserManagerWrapper.java b/src/com/android/settings/wrapper/UserManagerWrapper.java index eeb648bd102..4b4d2f4166a 100644 --- a/src/com/android/settings/wrapper/UserManagerWrapper.java +++ b/src/com/android/settings/wrapper/UserManagerWrapper.java @@ -41,4 +41,8 @@ public class UserManagerWrapper { public List getUsers() { return mUserManager.getUsers(); } + + public List getProfiles(int userHandle) { + return mUserManager.getProfiles(userHandle); + } } diff --git a/tests/robotests/src/com/android/settings/applications/PictureInPictureSettingsTest.java b/tests/robotests/src/com/android/settings/applications/PictureInPictureSettingsTest.java new file mode 100644 index 00000000000..d379dbd10a0 --- /dev/null +++ b/tests/robotests/src/com/android/settings/applications/PictureInPictureSettingsTest.java @@ -0,0 +1,170 @@ +/* + * 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.ArgumentMatchers.nullable; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.UserInfo; +import android.util.Pair; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.TestConfig; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.wrapper.UserManagerWrapper; +import com.android.settingslib.wrapper.PackageManagerWrapper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; + +import java.util.ArrayList; +import java.util.Collections; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class PictureInPictureSettingsTest { + + private static final int PRIMARY_USER_ID = 0; + private static final int PROFILE_USER_ID = 10; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Context mContext; + + private FakeFeatureFactory mFeatureFactory; + private PictureInPictureSettings mFragment; + @Mock + private PackageManagerWrapper mPackageManager; + @Mock + private UserManagerWrapper mUserManager; + private ArrayList mPrimaryUserPackages; + private ArrayList mProfileUserPackages; + private ArrayList mUsers; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + FakeFeatureFactory.setupForTest(mContext); + mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext); + mFragment = new PictureInPictureSettings(mPackageManager, mUserManager); + mPrimaryUserPackages = new ArrayList<>(); + mProfileUserPackages = new ArrayList<>(); + mUsers = new ArrayList<>(); + when(mPackageManager.getInstalledPackagesAsUser(anyInt(), eq(PRIMARY_USER_ID))) + .thenReturn(mPrimaryUserPackages); + when(mPackageManager.getInstalledPackagesAsUser(anyInt(), eq(PROFILE_USER_ID))) + .thenReturn(mProfileUserPackages); + when(mUserManager.getProfiles(anyInt())).thenReturn(mUsers); + } + + @Test + public void testCollectPipApps() { + PackageInfo primaryP1 = createPackage("Calculator", true); + PackageInfo primaryP2 = createPackage("Clock", false); + PackageInfo profileP1 = createPackage("Calculator", false); + PackageInfo profileP2 = createPackage("Clock", true); + + mPrimaryUserPackages.add(primaryP1); + mPrimaryUserPackages.add(primaryP2); + mProfileUserPackages.add(profileP1); + mProfileUserPackages.add(profileP2); + + ArrayList> apps = mFragment.collectPipApps(PRIMARY_USER_ID); + assertThat(containsPackages(apps, primaryP1, profileP2)); + assertThat(!containsPackages(apps, primaryP2, profileP1)); + } + + @Test + public void testAppSort() { + PackageInfo primaryP1 = createPackage("Android", true); + PackageInfo primaryP2 = createPackage("Boop", true); + PackageInfo primaryP3 = createPackage("Deck", true); + PackageInfo profileP1 = createPackage("Android", true); + PackageInfo profileP2 = createPackage("Cool", true); + PackageInfo profileP3 = createPackage("Fast", false); + + mPrimaryUserPackages.add(primaryP1); + mPrimaryUserPackages.add(primaryP2); + mPrimaryUserPackages.add(primaryP3); + mProfileUserPackages.add(profileP1); + mProfileUserPackages.add(profileP2); + mProfileUserPackages.add(profileP3); + + ArrayList> apps = mFragment.collectPipApps(PRIMARY_USER_ID); + Collections.sort(apps, new PictureInPictureSettings.AppComparator(null)); + assertThat(isOrdered(apps, primaryP1, profileP1, primaryP2, profileP2)); + } + + private boolean containsPackages(ArrayList> apps, + PackageInfo... packages) { + for (int i = 0; i < packages.length; i++) { + boolean found = false; + for (int j = 0; j < apps.size(); j++) { + if (apps.get(j).first == packages[i].applicationInfo) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + return true; + } + + private boolean isOrdered(ArrayList> apps, + PackageInfo... packages) { + if (apps.size() != packages.length) { + return false; + } + + for (int i = 0; i < packages.length; i++) { + if (packages[i].applicationInfo != apps.get(i).first) { + return false; + } + } + return true; + } + + private PackageInfo createPackage(String appTitle, boolean supportsPip) { + PackageInfo pi = new PackageInfo(); + ActivityInfo ai = new ActivityInfo(); + if (supportsPip) { + ai.flags |= ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE; + } + pi.activities = new ActivityInfo[1]; + pi.activities[0] = ai; + pi.applicationInfo = new ApplicationInfo(); + pi.applicationInfo.name = appTitle; + return pi; + } +} diff --git a/tests/robotests/src/com/android/settings/applications/UsageAccessDetailsTest.java b/tests/robotests/src/com/android/settings/applications/UsageAccessDetailsTest.java index 229057fb0a9..07acb13de02 100644 --- a/tests/robotests/src/com/android/settings/applications/UsageAccessDetailsTest.java +++ b/tests/robotests/src/com/android/settings/applications/UsageAccessDetailsTest.java @@ -21,6 +21,7 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.verify; import android.content.Context; +import android.os.RemoteException; import com.android.internal.logging.nano.MetricsProto; import com.android.settings.testutils.SettingsRobolectricTestRunner; @@ -65,4 +66,11 @@ public class UsageAccessDetailsTest { verify(mFeatureFactory.metricsFeatureProvider).action(nullable(Context.class), eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_USAGE_VIEW_DENY), eq("app")); } + + @Test + public void refreshUi_nullPackageInfo_shouldNotCrash() throws RemoteException { + mFragment.mPackageInfo = null; + mFragment.refreshUi(); + // should not crash + } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothSummaryUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSummaryUpdaterTest.java index e3f00d807b6..0c27412c551 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothSummaryUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSummaryUpdaterTest.java @@ -18,17 +18,25 @@ package com.android.settings.bluetooth; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.Context; +import android.util.Log; import com.android.settings.R; -import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.widget.SummaryUpdater.OnSummaryChangeListener; -import com.android.settingslib.bluetooth.CachedBluetoothDevice; -import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothAdapter; +import com.android.settingslib.bluetooth.LocalBluetoothManager; import org.junit.Before; import org.junit.Test; @@ -39,19 +47,9 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; -import java.util.ArrayList; import java.util.HashSet; -import java.util.List; import java.util.Set; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class BluetoothSummaryUpdaterTest { @@ -70,16 +68,33 @@ public class BluetoothSummaryUpdaterTest { @Mock private SummaryListener mListener; + // Disabled by default + private final boolean[] mAdapterEnabled = {false}; + // Not connected by default + private final int[] mAdapterConnectionState = {BluetoothAdapter.STATE_DISCONNECTED}; + // Not connected by default + private final boolean[] mDeviceConnected = {false, false}; + private final Set mBondedDevices = new HashSet<>(); private BluetoothSummaryUpdater mSummaryUpdater; @Before public void setUp() { MockitoAnnotations.initMocks(this); - when(mBluetoothManager.getBluetoothAdapter()).thenReturn(mBtAdapter); - when(mBtAdapter.isEnabled()).thenReturn(true); - when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTED); mContext = RuntimeEnvironment.application.getApplicationContext(); + doCallRealMethod().when(mListener).onSummaryChanged(anyString()); + // Setup mock adapter + when(mBluetoothManager.getBluetoothAdapter()).thenReturn(mBtAdapter); + doAnswer(invocation -> mAdapterEnabled[0]).when(mBtAdapter).isEnabled(); + doAnswer(invocation -> mAdapterConnectionState[0]).when(mBtAdapter).getConnectionState(); mSummaryUpdater = new BluetoothSummaryUpdater(mContext, mListener, mBluetoothManager); + // Setup first device + doReturn(DEVICE_NAME).when(mConnectedDevice).getName(); + doAnswer(invocation -> mDeviceConnected[0]).when(mConnectedDevice).isConnected(); + // Setup second device + doReturn(DEVICE_KEYBOARD_NAME).when(mConnectedKeyBoardDevice).getName(); + doAnswer(invocation -> mDeviceConnected[1]).when(mConnectedKeyBoardDevice) + .isConnected(); + doReturn(mBondedDevices).when(mBtAdapter).getBondedDevices(); } @Test @@ -98,7 +113,10 @@ public class BluetoothSummaryUpdaterTest { @Test public void register_true_shouldSendSummaryChange() { - prepareConnectedDevice(false); + mAdapterEnabled[0] = true; + mAdapterConnectionState[0] = BluetoothAdapter.STATE_CONNECTED; + mBondedDevices.add(mConnectedDevice); + mDeviceConnected[0] = true; mSummaryUpdater.register(true); @@ -108,7 +126,11 @@ public class BluetoothSummaryUpdaterTest { @Test public void onBluetoothStateChanged_btDisabled_shouldSendDisabledSummary() { - mSummaryUpdater.register(true); + // These states should be ignored + mAdapterConnectionState[0] = BluetoothAdapter.STATE_CONNECTED; + mBondedDevices.add(mConnectedDevice); + mDeviceConnected[0] = true; + mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF); verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_disabled)); @@ -116,68 +138,83 @@ public class BluetoothSummaryUpdaterTest { @Test public void onBluetoothStateChanged_btEnabled_connected_shouldSendConnectedSummary() { - prepareConnectedDevice(false); + mAdapterEnabled[0] = true; + mAdapterConnectionState[0] = BluetoothAdapter.STATE_CONNECTED; + mBondedDevices.add(mConnectedDevice); + mDeviceConnected[0] = true; - mSummaryUpdater.register(true); mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_ON); verify(mListener).onSummaryChanged( mContext.getString(R.string.bluetooth_connected_summary, DEVICE_NAME)); } + @Test + public void onBluetoothStateChanged_btEnabled_connectedMisMatch_shouldSendNotConnected() { + mAdapterEnabled[0] = true; + mAdapterConnectionState[0] = BluetoothAdapter.STATE_CONNECTED; + mBondedDevices.add(mConnectedDevice); + // State mismatch + mDeviceConnected[0] = false; + + mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_ON); + + verify(mListener).onSummaryChanged(mContext.getString(R.string.disconnected)); + } + @Test public void onBluetoothStateChanged_btEnabled_notConnected_shouldSendDisconnectedMessage() { - when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_DISCONNECTED); - mSummaryUpdater.register(true); + mAdapterEnabled[0] = true; + mAdapterConnectionState[0] = BluetoothAdapter.STATE_DISCONNECTED; + mBondedDevices.add(mConnectedDevice); + // This should be ignored + mDeviceConnected[0] = true; + mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_TURNING_ON); - verify(mListener).onSummaryChanged( - mContext.getString(R.string.disconnected)); + verify(mListener).onSummaryChanged(mContext.getString(R.string.disconnected)); } @Test public void onBluetoothStateChanged_ConnectedDisabledEnabled_shouldSendDisconnectedSummary() { - final boolean[] connected = {false}; - final List devices = new ArrayList<>(); - devices.add(mock(CachedBluetoothDevice.class)); - doAnswer(invocation -> connected[0]).when(devices.get(0)).isConnected(); - when(mBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy()) - .thenReturn(devices); - when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_DISCONNECTED); - prepareConnectedDevice(false); + mAdapterEnabled[0] = true; + mAdapterConnectionState[0] = BluetoothAdapter.STATE_DISCONNECTED; + mBondedDevices.add(mConnectedDevice); + mDeviceConnected[0] = false; mSummaryUpdater.register(true); verify(mListener).onSummaryChanged(mContext.getString(R.string.disconnected)); - connected[0] = true; - when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTED); + mAdapterConnectionState[0] = BluetoothAdapter.STATE_CONNECTED; + mDeviceConnected[0] = true; mSummaryUpdater.onConnectionStateChanged(null /* device */, BluetoothAdapter.STATE_CONNECTED); verify(mListener).onSummaryChanged( mContext.getString(R.string.bluetooth_connected_summary, DEVICE_NAME)); + mAdapterEnabled[0] = false; mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF); verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_disabled)); - connected[0] = false; + // Turning ON means not enabled mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_TURNING_ON); + // There should still be only one invocation of disabled message + verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_disabled)); + + mAdapterEnabled[0] = true; + mDeviceConnected[0] = false; + mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_ON); verify(mListener, times(2)).onSummaryChanged(mContext.getString(R.string.disconnected)); verify(mListener, times(4)).onSummaryChanged(anyString()); } @Test public void onConnectionStateChanged_connected_shouldSendConnectedMessage() { - final List devices = new ArrayList<>(); - devices.add(mock(CachedBluetoothDevice.class)); - when(devices.get(0).isConnected()).thenReturn(true); - when(mBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy()) - .thenReturn(devices); - when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_DISCONNECTED); - prepareConnectedDevice(false); + mAdapterEnabled[0] = true; + mAdapterConnectionState[0] = BluetoothAdapter.STATE_CONNECTED; + mBondedDevices.add(mConnectedDevice); + mDeviceConnected[0] = true; - mSummaryUpdater.register(true); - - when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTED); mSummaryUpdater.onConnectionStateChanged(null /* device */, BluetoothAdapter.STATE_CONNECTED); @@ -187,7 +224,22 @@ public class BluetoothSummaryUpdaterTest { @Test public void onConnectionStateChanged_inconsistentState_shouldSendDisconnectedMessage() { - mSummaryUpdater.register(true); + mAdapterEnabled[0] = true; + mAdapterConnectionState[0] = BluetoothAdapter.STATE_DISCONNECTED; + mBondedDevices.add(mConnectedDevice); + mDeviceConnected[0] = false; + + mSummaryUpdater.onConnectionStateChanged(null /* device */, + BluetoothAdapter.STATE_CONNECTED); + + verify(mListener).onSummaryChanged(mContext.getString(R.string.disconnected)); + } + + @Test + public void onConnectionStateChanged_noBondedDevice_shouldSendDisconnectedMessage() { + mAdapterEnabled[0] = true; + mAdapterConnectionState[0] = BluetoothAdapter.STATE_CONNECTED; + mSummaryUpdater.onConnectionStateChanged(null /* device */, BluetoothAdapter.STATE_CONNECTED); @@ -197,8 +249,10 @@ public class BluetoothSummaryUpdaterTest { @Test public void onConnectionStateChanged_connecting_shouldSendConnectingMessage() { - mSummaryUpdater.register(true); - when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTING); + // No need for bonded devices + mAdapterEnabled[0] = true; + mAdapterConnectionState[0] = BluetoothAdapter.STATE_CONNECTING; + mSummaryUpdater.onConnectionStateChanged(null /* device */, BluetoothAdapter.STATE_CONNECTING); @@ -207,8 +261,10 @@ public class BluetoothSummaryUpdaterTest { @Test public void onConnectionStateChanged_disconnecting_shouldSendDisconnectingMessage() { - mSummaryUpdater.register(true); - when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_DISCONNECTING); + // No need for bonded devices + mAdapterEnabled[0] = true; + mAdapterConnectionState[0] = BluetoothAdapter.STATE_DISCONNECTING; + mSummaryUpdater.onConnectionStateChanged(null /* device */, BluetoothAdapter.STATE_DISCONNECTING); @@ -217,7 +273,8 @@ public class BluetoothSummaryUpdaterTest { @Test public void getConnectedDeviceSummary_hasConnectedDevice_returnOneDeviceSummary() { - prepareConnectedDevice(false); + mBondedDevices.add(mConnectedDevice); + mDeviceConnected[0] = true; final String expectedSummary = mContext.getString(R.string.bluetooth_connected_summary, DEVICE_NAME); @@ -226,28 +283,16 @@ public class BluetoothSummaryUpdaterTest { @Test public void getConnectedDeviceSummary_multipleDevices_returnMultipleDevicesSummary() { - prepareConnectedDevice(true); + mBondedDevices.add(mConnectedDevice); + mBondedDevices.add(mConnectedKeyBoardDevice); + mDeviceConnected[0] = true; + mDeviceConnected[1] = true; final String expectedSummary = mContext.getString( R.string.bluetooth_connected_multiple_devices_summary); assertThat(mSummaryUpdater.getConnectedDeviceSummary()).isEqualTo(expectedSummary); } - private void prepareConnectedDevice(boolean multipleDevices) { - final Set devices = new HashSet<>(); - doReturn(DEVICE_NAME).when(mConnectedDevice).getName(); - doReturn(true).when(mConnectedDevice).isConnected(); - devices.add(mConnectedDevice); - if (multipleDevices) { - // Add one more device if we need to test multiple devices - doReturn(DEVICE_KEYBOARD_NAME).when(mConnectedKeyBoardDevice).getName(); - doReturn(true).when(mConnectedKeyBoardDevice).isConnected(); - devices.add(mConnectedKeyBoardDevice); - } - - doReturn(devices).when(mBtAdapter).getBondedDevices(); - } - private class SummaryListener implements OnSummaryChangeListener { String summary; diff --git a/tests/robotests/src/com/android/settings/core/instrumentation/MetricsFeatureProviderTest.java b/tests/robotests/src/com/android/settings/core/instrumentation/MetricsFeatureProviderTest.java index ea33c83391f..ff91c4099d3 100644 --- a/tests/robotests/src/com/android/settings/core/instrumentation/MetricsFeatureProviderTest.java +++ b/tests/robotests/src/com/android/settings/core/instrumentation/MetricsFeatureProviderTest.java @@ -28,6 +28,8 @@ import com.android.settings.overlay.FeatureFactory; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; @@ -42,24 +44,35 @@ import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class MetricsFeatureProviderTest { + private static int CATEGORY = 10; + private static boolean SUBTYPE_BOOLEAN = true; + private static int SUBTYPE_INTEGER = 1; + private static long ELAPSED_TIME = 1000; + + @Mock private LogWriter mockLogWriter; + @Mock private VisibilityLoggerMixin mockVisibilityLogger; - @Mock - private LogWriter mLogWriter; private Context mContext; private MetricsFeatureProvider mProvider; + @Captor + private ArgumentCaptor mPairCaptor; + @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; mProvider = new MetricsFeatureProvider(); List writers = new ArrayList<>(); - writers.add(mLogWriter); + writers.add(mockLogWriter); ReflectionHelpers.setField(mProvider, "mLoggerWriters", writers); + + when(mockVisibilityLogger.elapsedTimeSinceVisible()).thenReturn(ELAPSED_TIME); } @Test @@ -77,7 +90,7 @@ public class MetricsFeatureProviderTest { mProvider.logDashboardStartIntent(mContext, null /* intent */, MetricsEvent.SETTINGS_GESTURES); - verifyNoMoreInteractions(mLogWriter); + verifyNoMoreInteractions(mockLogWriter); } @Test @@ -86,7 +99,7 @@ public class MetricsFeatureProviderTest { mProvider.logDashboardStartIntent(mContext, intent, MetricsEvent.SETTINGS_GESTURES); - verify(mLogWriter).action( + verify(mockLogWriter).action( eq(mContext), eq(MetricsEvent.ACTION_SETTINGS_TILE_CLICK), anyString(), @@ -99,10 +112,32 @@ public class MetricsFeatureProviderTest { mProvider.logDashboardStartIntent(mContext, intent, MetricsEvent.SETTINGS_GESTURES); - verify(mLogWriter).action( + verify(mockLogWriter).action( eq(mContext), eq(MetricsEvent.ACTION_SETTINGS_TILE_CLICK), anyString(), eq(Pair.create(MetricsEvent.FIELD_CONTEXT, MetricsEvent.SETTINGS_GESTURES))); } + + @Test + public void action_BooleanLogsElapsedTime() { + mProvider.action(mockVisibilityLogger, CATEGORY, SUBTYPE_BOOLEAN); + verify(mockLogWriter).action(eq(CATEGORY), eq(SUBTYPE_BOOLEAN), mPairCaptor.capture()); + + Pair value = mPairCaptor.getValue(); + assertThat(value.first instanceof Integer).isTrue(); + assertThat((int) value.first).isEqualTo(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS); + assertThat(value.second).isEqualTo(ELAPSED_TIME); + } + + @Test + public void action_IntegerLogsElapsedTime() { + mProvider.action(mockVisibilityLogger, CATEGORY, SUBTYPE_INTEGER); + verify(mockLogWriter).action(eq(CATEGORY), eq(SUBTYPE_INTEGER), mPairCaptor.capture()); + + Pair value = mPairCaptor.getValue(); + assertThat(value.first instanceof Integer).isTrue(); + assertThat((int) value.first).isEqualTo(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS); + assertThat(value.second).isEqualTo(ELAPSED_TIME); + } } diff --git a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterTest.java b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterTest.java index 1e4ad79afc5..6b804656c90 100644 --- a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterTest.java @@ -203,7 +203,7 @@ public class SuggestionAdapterTest { public void onBindViewHolder_v2_itemViewShouldHandleClick() throws PendingIntent.CanceledException { final List packages = makeSuggestionsV2("pkg1"); - setupSuggestions(mActivity, null /* suggestionV1 */ , packages); + setupSuggestions(mActivity, null /* suggestionV1 */, packages); mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0); mSuggestionHolder.itemView.performClick(); @@ -233,6 +233,22 @@ public class SuggestionAdapterTest { assertThat(itemView.getChildCount()).isEqualTo(1); } + @Test + public void getSuggestionsV2_shouldReturnSuggestionWhenMatch() { + final List suggestionsV2 = makeSuggestionsV2("pkg1"); + setupSuggestions(mActivity, null /* suggestionV1 */, suggestionsV2); + + assertThat(mSuggestionAdapter.getSuggestion(0)).isNull(); + assertThat(mSuggestionAdapter.getSuggestionsV2(0)).isNotNull(); + + List suggestionsV1 = makeSuggestions("pkg1"); + setupSuggestions(mActivity, suggestionsV1, null /* suggestionV2 */); + + assertThat(mSuggestionAdapter.getSuggestionsV2(0)).isNull(); + assertThat(mSuggestionAdapter.getSuggestion(0)).isNotNull(); + + } + private void setupSuggestions(Context context, List suggestions, List suggestionsV2) { mSuggestionAdapter = new SuggestionAdapter(context, suggestions, suggestionsV2, diff --git a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImplTest.java index f1568fe68fc..0650d5b0a22 100644 --- a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImplTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImplTest.java @@ -72,6 +72,7 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -430,14 +431,35 @@ public class SuggestionFeatureProviderImplTest { } @Test - public void hasUsedNightDisplay_returnsTrue_ifPreviouslyActivated() { - Secure.putLong(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, 1L); + public void hasUsedNightDisplay_returnsTrue_ifPreviouslyActivatedAndManual() { + Secure.putString(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, + LocalDateTime.now().toString()); + Secure.putInt(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_AUTO_MODE, 1); assertThat(mProvider.hasUsedNightDisplay(mContext)).isTrue(); } @Test public void nightDisplaySuggestion_isCompleted_ifPreviouslyActivated() { - Secure.putLong(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, 1L); + Secure.putString(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, + LocalDateTime.now().toString()); + final ComponentName componentName = + new ComponentName(mContext, NightDisplaySuggestionActivity.class); + assertThat(mProvider.isSuggestionCompleted(mContext, componentName)).isTrue(); + } + + @Test + public void nightDisplaySuggestion_isCompleted_ifNonManualMode() { + Secure.putInt(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_AUTO_MODE, 1); + final ComponentName componentName = + new ComponentName(mContext, NightDisplaySuggestionActivity.class); + assertThat(mProvider.isSuggestionCompleted(mContext, componentName)).isTrue(); + } + + @Test + public void nightDisplaySuggestion_isCompleted_ifPreviouslyCleared() { + Secure.putString(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, + null); + Secure.putInt(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_AUTO_MODE, 1); final ComponentName componentName = new ComponentName(mContext, NightDisplaySuggestionActivity.class); assertThat(mProvider.isSuggestionCompleted(mContext, componentName)).isTrue(); diff --git a/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java index a001aafcdb1..13f73742d65 100644 --- a/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java +++ b/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java @@ -17,7 +17,11 @@ package com.android.settings.development; 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; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; @@ -108,6 +112,9 @@ public class DevelopmentSettingsDashboardFragmentTest { } @Test + @Config(shadows = { + ShadowPictureColorModePreferenceController.class + }) public void searchIndex_pageEnabled_shouldNotAddKeysToNonIndexable() { final Context appContext = RuntimeEnvironment.application; DevelopmentSettingsEnabler.setDevelopmentSettingsEnabled(appContext, true); @@ -161,6 +168,24 @@ public class DevelopmentSettingsDashboardFragmentTest { .isFalse(); } + @Test + public void onOemUnlockDialogConfirmed_shouldCallControllerOemConfirmed() { + final OemUnlockPreferenceController controller = mock(OemUnlockPreferenceController.class); + doReturn(controller).when(mDashboard).getDevelopmentOptionsController( + OemUnlockPreferenceController.class); + mDashboard.onOemUnlockDialogConfirmed(); + verify(controller).onOemUnlockConfirmed(); + } + + @Test + public void onOemUnlockDialogConfirmed_shouldCallControllerOemDismissed() { + final OemUnlockPreferenceController controller = mock(OemUnlockPreferenceController.class); + doReturn(controller).when(mDashboard).getDevelopmentOptionsController( + OemUnlockPreferenceController.class); + mDashboard.onOemUnlockDialogDismissed(); + verify(controller).onOemUnlockDismissed(); + } + @Implements(EnableDevelopmentSettingWarningDialog.class) public static class ShadowEnableDevelopmentSettingWarningDialog { @@ -176,4 +201,13 @@ public class DevelopmentSettingsDashboardFragmentTest { mShown = true; } } + + @Implements(PictureColorModePreferenceController.class) + public static class ShadowPictureColorModePreferenceController { + + @Implementation + public boolean isAvailable() { + return true; + } + } } diff --git a/tests/robotests/src/com/android/settings/development/OemUnlockPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/OemUnlockPreferenceControllerTest.java new file mode 100644 index 00000000000..13678703bd8 --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/OemUnlockPreferenceControllerTest.java @@ -0,0 +1,201 @@ +/* + * 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.development; + +import static com.android.settings.development.DevelopmentOptionsActivityRequestCodes + .REQUEST_CODE_ENABLE_OEM_UNLOCK; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.os.UserManager; +import android.service.oemlock.OemLockManager; +import android.support.v7.preference.PreferenceScreen; +import android.telephony.TelephonyManager; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.RestrictedSwitchPreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class OemUnlockPreferenceControllerTest { + + @Mock + private Context mContext; + @Mock + private Activity mActivity; + @Mock + private DevelopmentSettingsDashboardFragment mFragment; + @Mock + private RestrictedSwitchPreference mPreference; + @Mock + private PreferenceScreen mPreferenceScreen; + @Mock + private OemLockManager mOemLockManager; + @Mock + private UserManager mUserManager; + @Mock + private TelephonyManager mTelephonyManager; + @Mock + private Resources mResources; + private OemUnlockPreferenceController mController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + when(mContext.getSystemService(Context.OEM_LOCK_SERVICE)).thenReturn(mOemLockManager); + when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager); + when(mContext.getResources()).thenReturn(mResources); + mController = new OemUnlockPreferenceController(mContext, mActivity, mFragment); + when(mPreferenceScreen.findPreference(mController.getPreferenceKey())).thenReturn( + mPreference); + mController.displayPreference(mPreferenceScreen); + } + + @Test + public void isAvailable_shouldReturnTrueWhenOemLockManagerIsNotNull() { + boolean returnValue = mController.isAvailable(); + + assertThat(returnValue).isTrue(); + } + + @Test + public void isAvailable_shouldReturnFalseWhenOemLockManagerIsNull() { + when(mContext.getSystemService(Context.OEM_LOCK_SERVICE)).thenReturn(null); + mController = new OemUnlockPreferenceController(mContext, mActivity, mFragment); + boolean returnValue = mController.isAvailable(); + + assertThat(returnValue).isFalse(); + } + + @Test + public void onPreferenceChanged_turnOnUnlock() { + mController = spy(mController); + doReturn(false).when(mController).showKeyguardConfirmation(mResources, + REQUEST_CODE_ENABLE_OEM_UNLOCK); + doNothing().when(mController).confirmEnableOemUnlock(); + mController.onPreferenceChange(null, true); + + verify(mController).confirmEnableOemUnlock(); + } + + @Test + public void onPreferenceChanged_turnOffUnlock() { + mController.onPreferenceChange(null, false); + + verify(mOemLockManager).setOemUnlockAllowedByUser(false); + } + + @Test + public void updateState_preferenceShouldBeCheckedAndShouldBeDisabled() { + mController = spy(mController); + when(mOemLockManager.isOemUnlockAllowed()).thenReturn(true); + doReturn(true).when(mController).isOemUnlockAllowedByUserAndCarrier(); + when(mOemLockManager.isDeviceOemUnlocked()).thenReturn(true); + mController.updateState(mPreference); + + verify(mPreference).setChecked(true); + verify(mPreference).setEnabled(false); + } + + @Test + public void updateState_preferenceShouldBeUncheckedAndShouldBeDisabled() { + mController = spy(mController); + when(mOemLockManager.isOemUnlockAllowed()).thenReturn(false); + doReturn(true).when(mController).isOemUnlockAllowedByUserAndCarrier(); + when(mOemLockManager.isDeviceOemUnlocked()).thenReturn(true); + mController.updateState(mPreference); + + verify(mPreference).setChecked(false); + verify(mPreference).setEnabled(false); + } + + @Test + public void updateState_preferenceShouldBeCheckedAndShouldBeEnabled() { + mController = spy(mController); + when(mOemLockManager.isOemUnlockAllowed()).thenReturn(true); + doReturn(true).when(mController).isOemUnlockAllowedByUserAndCarrier(); + when(mOemLockManager.isDeviceOemUnlocked()).thenReturn(false); + mController.updateState(mPreference); + + verify(mPreference).setChecked(true); + verify(mPreference).setEnabled(true); + } + + @Test + public void onActivityResult_shouldReturnTrue() { + final boolean result = mController.onActivityResult(REQUEST_CODE_ENABLE_OEM_UNLOCK, + Activity.RESULT_OK, null); + + assertThat(result).isTrue(); + } + + @Test + public void onActivityResult_shouldReturnFalse() { + final boolean result = mController.onActivityResult(123454, + 1434, null); + + assertThat(result).isFalse(); + } + + @Test + public void onDeveloperOptionsEnabled_preferenceShouldCheckRestriction() { + mController = spy(mController); + doReturn(false).when(mController).isOemUnlockAllowedByUserAndCarrier(); + when(mPreference.isEnabled()).thenReturn(true); + mController.onDeveloperOptionsEnabled(); + + verify(mPreference).checkRestrictionAndSetDisabled(UserManager.DISALLOW_FACTORY_RESET); + + } + + @Test + public void onDeveloperOptionsDisabled_preferenceShouldCheckRestriction() { + mController = spy(mController); + doReturn(false).when(mController).isOemUnlockAllowedByUserAndCarrier(); + when(mPreference.isEnabled()).thenReturn(true); + mController.onDeveloperOptionsDisabled(); + + verify(mPreference).checkRestrictionAndSetDisabled(UserManager.DISALLOW_FACTORY_RESET); + + } + + @Test + public void onOemUnlockConfirmed_oemManagerShouldSetUnlockAllowedByUser() { + mController.onOemUnlockConfirmed(); + + verify(mOemLockManager).setOemUnlockAllowedByUser(true); + } +} diff --git a/tests/robotests/src/com/android/settings/development/PictureColorModePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/PictureColorModePreferenceControllerTest.java new file mode 100644 index 00000000000..5cf4e10a4f2 --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/PictureColorModePreferenceControllerTest.java @@ -0,0 +1,137 @@ +/* + * 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.development; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.Resources; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class PictureColorModePreferenceControllerTest { + + @Mock + private ColorModePreference mPreference; + @Mock + private Context mContext; + @Mock + private PreferenceScreen mPreferenceScreen; + @Mock + private Resources mResources; + + private Lifecycle mLifecycle; + private PictureColorModePreferenceController mController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mLifecycle = new Lifecycle(); + mController = new PictureColorModePreferenceController(mContext, mLifecycle); + when(mPreferenceScreen.findPreference(mController.getPreferenceKey())).thenReturn( + mPreference); + when(mContext.getResources()).thenReturn(mResources); + when(mResources.getIntArray(R.array.color_mode_ids)).thenReturn(new int[0]); + mController.displayPreference(mPreferenceScreen); + } + + @Test + public void isAvailable_shouldReturnFalseWhenWideColorGambit() { + mController = spy(mController); + doReturn(2).when(mController).getColorModeDescriptionsSize(); + doReturn(true).when(mController).isWideColorGamut(); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_shouldReturnTrueWhenNotWideColorGambit() { + mController = spy(mController); + doReturn(2).when(mController).getColorModeDescriptionsSize(); + doReturn(false).when(mController).isWideColorGamut(); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_shouldReturnFalseWhenColorCountIsOne() { + mController = spy(mController); + doReturn(1).when(mController).getColorModeDescriptionsSize(); + doReturn(true).when(mController).isWideColorGamut(); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_shouldReturnTrueWhenColorCountIsTwo() { + mController = spy(mController); + doReturn(2).when(mController).getColorModeDescriptionsSize(); + doReturn(false).when(mController).isWideColorGamut(); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void onDeveloperOptionEnabled_shouldEnablePreference() { + mController = spy(mController); + doReturn(true).when(mController).isAvailable(); + mController.onDeveloperOptionsEnabled(); + + verify(mPreference).setEnabled(true); + } + + @Test + public void onDeveloperOptionDisabled_shouldDisablePreference() { + mController = spy(mController); + doReturn(true).when(mController).isAvailable(); + mController.onDeveloperOptionsDisabled(); + + verify(mPreference).setEnabled(false); + } + + @Test + public void onResume_shouldStartListening() { + mLifecycle.onResume(); + + verify(mPreference).startListening(); + } + + @Test + public void onPause_shouldStopListening() { + mLifecycle.onPause(); + + verify(mPreference).stopListening(); + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowSecureSettings.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowSecureSettings.java index 0f61a5d816e..8b2a27bd1fa 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowSecureSettings.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowSecureSettings.java @@ -36,7 +36,11 @@ public class ShadowSecureSettings { int userHandle) { final Table userTable = getUserTable(resolver); synchronized (userTable) { - userTable.put(userHandle, name, value); + if (value != null) { + userTable.put(userHandle, name, value); + } else { + userTable.remove(userHandle, name); + } return true; } }