Merge "Fixing issue with PiP settings not showing apps for other profiles."
diff --git a/res/layout/choose_lock_pattern_common.xml b/res/layout/choose_lock_pattern_common.xml
index 65135dd..949d130 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">
<com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient
android:id="@+id/topLayout"
diff --git a/src/com/android/settings/applications/AppInfoBase.java b/src/com/android/settings/applications/AppInfoBase.java
index b06c070..ddf68c1 100644
--- a/src/com/android/settings/applications/AppInfoBase.java
+++ b/src/com/android/settings/applications/AppInfoBase.java
@@ -217,7 +217,9 @@
@Override
public void onPackageListChanged() {
- refreshUi();
+ if (!refreshUi()) {
+ setIntentAndFinish(true, true);
+ }
}
public static void startAppInfoFragment(Class<?> fragment, int titleRes,
diff --git a/src/com/android/settings/applications/UsageAccessDetails.java b/src/com/android/settings/applications/UsageAccessDetails.java
index e40ae37..253ddfd 100644
--- a/src/com/android/settings/applications/UsageAccessDetails.java
+++ b/src/com/android/settings/applications/UsageAccessDetails.java
@@ -137,6 +137,9 @@
@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 7d2cc18..662cd70 100644
--- a/src/com/android/settings/bluetooth/BluetoothSummaryUpdater.java
+++ b/src/com/android/settings/bluetooth/BluetoothSummaryUpdater.java
@@ -42,31 +42,21 @@
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 @@
return;
}
if (listening) {
- mEnabled = mBluetoothAdapter.isEnabled();
- mConnectionState = mBluetoothAdapter.getConnectionState();
notifyChangeIfNeeded();
mBluetoothManager.getEventManager().registerCallback(this);
} else {
@@ -103,10 +91,10 @@
@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 @@
}
}
- 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<CachedBluetoothDevice> 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<CachedBluetoothDevice> getDevices() {
- return mBluetoothManager != null
- ? mBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy()
- : null;
- }
-
@VisibleForTesting
String getConnectedDeviceSummary() {
String deviceName = null;
int count = 0;
final Set<BluetoothDevice> 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 @@
}
}
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 bfb69e7..a5d0715 100644
--- a/src/com/android/settings/core/InstrumentedPreferenceFragment.java
+++ b/src/com/android/settings/core/InstrumentedPreferenceFragment.java
@@ -65,4 +65,8 @@
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 e7628e8..3196f76 100644
--- a/src/com/android/settings/core/instrumentation/EventLogWriter.java
+++ b/src/com/android/settings/core/instrumentation/EventLogWriter.java
@@ -18,6 +18,7 @@
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 @@
*/
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 @@
MetricsLogger.hidden(context, category);
}
+ public void action(int category, int value, Pair<Integer, Object>... 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<Integer, Object> pair : taggedData) {
+ logMaker.addTaggedData(pair.first, pair.second);
+ }
+ mMetricsLogger.write(logMaker);
+ }
+ }
+
+ public void action(int category, boolean value, Pair<Integer, Object>... taggedData) {
+ action(category, value ? 1 : 0, taggedData);
+ }
+
public void action(Context context, int category, Pair<Integer, Object>... taggedData) {
action(context, category, "", taggedData);
}
@@ -52,12 +73,16 @@
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 584217d..062d46f 100644
--- a/src/com/android/settings/core/instrumentation/LogWriter.java
+++ b/src/com/android/settings/core/instrumentation/LogWriter.java
@@ -34,6 +34,16 @@
void hidden(Context context, int category);
/**
+ * Logs a user action.
+ */
+ void action(int category, int value, Pair<Integer, Object>... taggedData);
+
+ /**
+ * Logs a user action.
+ */
+ void action(int category, boolean value, Pair<Integer, Object>... taggedData);
+
+ /**
* Logs an user action.
*/
void action(Context context, int category, Pair<Integer, Object>... taggedData);
@@ -45,12 +55,16 @@
/**
* 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 afdec55..532ec66 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.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 @@
}
}
+ /**
+ * 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<Integer, Object>... 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 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 @@
// 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<Integer, Object> 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 bbdf8c9..697f0a3 100644
--- a/src/com/android/settings/core/instrumentation/SettingSuggestionsLogWriter.java
+++ b/src/com/android/settings/core/instrumentation/SettingSuggestionsLogWriter.java
@@ -46,6 +46,14 @@
}
@Override
+ public void action(int category, int value, Pair<Integer, Object>... taggedData) {
+ }
+
+ @Override
+ public void action(int category, boolean value, Pair<Integer, Object>... 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 8de35ad..2fe2a3b 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.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 @@
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 @@
@Override
public void onResume() {
+ mVisibleTimestamp = SystemClock.elapsedRealtime();
if (mMetricsFeature != null && mMetricsCategory != METRICS_CATEGORY_UNKNOWN) {
mMetricsFeature.visible(null /* context */, mSourceMetricsCategory, mMetricsCategory);
}
@@ -66,6 +69,7 @@
@Override
public void onPause() {
+ mVisibleTimestamp = 0;
if (mMetricsFeature != null && mMetricsCategory != METRICS_CATEGORY_UNKNOWN) {
mMetricsFeature.hidden(null /* context */, mMetricsCategory);
}
@@ -85,4 +89,12 @@
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/DashboardAdapter.java b/src/com/android/settings/dashboard/DashboardAdapter.java
index 741067a..5df66f1 100644
--- a/src/com/android/settings/dashboard/DashboardAdapter.java
+++ b/src/com/android/settings/dashboard/DashboardAdapter.java
@@ -23,6 +23,7 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
+import android.service.settings.suggestions.Suggestion;
import android.support.annotation.VisibleForTesting;
import android.support.v7.util.DiffUtil;
import android.support.v7.widget.LinearLayoutManager;
@@ -62,6 +63,7 @@
implements SummaryLoader.SummaryConsumer {
public static final String TAG = "DashboardAdapter";
private static final String STATE_SUGGESTION_LIST = "suggestion_list";
+ private static final String STATE_SUGGESTION_LIST_V2 = "suggestion_list_v2";
private static final String STATE_CATEGORY_LIST = "category_list";
private static final String STATE_SUGGESTIONS_SHOWN_LOGGED = "suggestions_shown_logged";
@@ -99,7 +101,10 @@
public DashboardAdapter(Context context, Bundle savedInstanceState,
List<Condition> conditions, SuggestionParser suggestionParser,
SuggestionDismissController.Callback callback) {
+
+ // @deprecated In favor of suggestionsV2 below.
List<Tile> suggestions = null;
+ List<Suggestion> suggestionsV2 = null;
DashboardCategory category = null;
int suggestionConditionMode = DashboardData.HEADER_MODE_DEFAULT;
@@ -116,6 +121,7 @@
if (savedInstanceState != null) {
suggestions = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST);
+ suggestionsV2 = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST_V2);
category = savedInstanceState.getParcelable(STATE_CATEGORY_LIST);
suggestionConditionMode = savedInstanceState.getInt(
STATE_SUGGESTION_CONDITION_MODE, suggestionConditionMode);
@@ -128,6 +134,7 @@
mDashboardData = new DashboardData.Builder()
.setConditions(conditions)
.setSuggestions(suggestions)
+ .setSuggestionsV2(suggestionsV2)
.setCategory(category)
.setSuggestionConditionMode(suggestionConditionMode)
.build();
@@ -137,8 +144,12 @@
return mDashboardData.getSuggestions();
}
- public void setCategoriesAndSuggestions(DashboardCategory category,
- List<Tile> suggestions) {
+ /**
+ * @deprecated in favor of {@link #setCategory(DashboardCategory)} and
+ * {@link #setSuggestionsV2(List)}.
+ */
+ @Deprecated
+ public void setCategoriesAndSuggestions(DashboardCategory category, List<Tile> suggestions) {
tintIcons(category, suggestions);
final DashboardData prevData = mDashboardData;
@@ -168,6 +179,16 @@
}
}
+ public void setSuggestionsV2(List<Suggestion> data) {
+ // TODO: Tint icon
+ final DashboardData prevData = mDashboardData;
+ mDashboardData = new DashboardData.Builder(prevData)
+ .setSuggestionsV2(data)
+ .build();
+ notifyDashboardDataChanged(prevData);
+ // TODO: Replicate the metrics logging from setCategoriesAndSuggestions()
+ }
+
public void setCategory(DashboardCategory category) {
tintIcons(category, null);
final DashboardData prevData = mDashboardData;
@@ -187,6 +208,10 @@
notifyDashboardDataChanged(prevData);
}
+ /**
+ * @deprecated in favor of {@link #onSuggestionDismissed(Suggestion)}.
+ */
+ @Deprecated
public void onSuggestionDismissed(Tile suggestion) {
final List<Tile> suggestions = mDashboardData.getSuggestions();
if (suggestions == null || suggestions.isEmpty()) {
@@ -205,6 +230,24 @@
}
}
+ public void onSuggestionDismissed(Suggestion suggestion) {
+ final List<Suggestion> list = mDashboardData.getSuggestionsV2();
+ if (list == null || list.size() == 0) {
+ return;
+ }
+ if (list.size() == 1) {
+ // The only suggestion is dismissed, and the the empty suggestion container will
+ // remain as the dashboard item. Need to refresh the dashboard list.
+ final DashboardData prevData = mDashboardData;
+ mDashboardData = new DashboardData.Builder(prevData)
+ .setSuggestionsV2(null)
+ .build();
+ notifyDashboardDataChanged(prevData);
+ } else {
+ mSuggestionAdapter.removeSuggestion(suggestion);
+ }
+ }
+
@Override
public void notifySummaryChanged(Tile tile) {
final int position = mDashboardData.getPositionByTile(tile);
@@ -303,10 +346,18 @@
return mDashboardData.getItemEntityById(itemId);
}
+ /**
+ * @deprecated in favor of {@link #getSuggestionV2(int)}.
+ */
+ @Deprecated
public Tile getSuggestion(int position) {
return mSuggestionAdapter.getSuggestion(position);
}
+ public Suggestion getSuggestionV2(int position) {
+ return mSuggestionAdapter.getSuggestionsV2(position);
+ }
+
@VisibleForTesting
void notifyDashboardDataChanged(DashboardData prevData) {
if (mFirstFrameDrawn && prevData != null) {
@@ -426,15 +477,30 @@
int position) {
// If there is suggestions to show, it will be at position 0 as we don't show the suggestion
// header anymore.
+ final List<Suggestion> suggestionsV2 = mDashboardData.getSuggestionsV2();
final List<Tile> suggestions = mDashboardData.getSuggestions();
- if (position == SUGGESTION_CONDITION_HEADER_POSITION
- && suggestions != null && suggestions.size() > 0) {
- mSuggestionAdapter = new SuggestionAdapter(mContext, (List<Tile>)
- mDashboardData.getItemEntityByPosition(position), mSuggestionsShownLogged);
- mSuggestionDismissHandler = new SuggestionDismissController(mContext,
- holder.data, mSuggestionParser, mCallback);
- holder.data.setAdapter(mSuggestionAdapter);
- } else {
+
+ boolean conditionOnly = true;
+ if (position == SUGGESTION_CONDITION_HEADER_POSITION) {
+ if (suggestions != null && suggestions.size() > 0) {
+ conditionOnly = false;
+ mSuggestionAdapter = new SuggestionAdapter(mContext, (List<Tile>)
+ mDashboardData.getItemEntityByPosition(position),
+ null, mSuggestionsShownLogged);
+ mSuggestionDismissHandler = new SuggestionDismissController(mContext,
+ holder.data, mSuggestionParser, mCallback);
+ holder.data.setAdapter(mSuggestionAdapter);
+ } else if (suggestionsV2 != null && suggestionsV2.size() > 0) {
+ conditionOnly = false;
+ mSuggestionAdapter = new SuggestionAdapter(mContext, null,
+ (List<Suggestion>) mDashboardData.getItemEntityByPosition(position),
+ mSuggestionsShownLogged);
+ mSuggestionDismissHandler = new SuggestionDismissController(mContext,
+ holder.data, null /* parser */, mCallback);
+ holder.data.setAdapter(mSuggestionAdapter);
+ }
+ }
+ if (conditionOnly) {
ConditionAdapter adapter = new ConditionAdapter(mContext,
(List<Condition>) mDashboardData.getItemEntityByPosition(position),
mDashboardData.getSuggestionConditionMode());
@@ -482,11 +548,16 @@
}
void onSaveInstanceState(Bundle outState) {
- final List<Tile> suggestions = mDashboardData.getSuggestions();
final DashboardCategory category = mDashboardData.getCategory();
+ final List<Tile> suggestions = mDashboardData.getSuggestions();
+ final List<Suggestion> suggestionV2 = mDashboardData.getSuggestionsV2();
if (suggestions != null) {
outState.putParcelableArrayList(STATE_SUGGESTION_LIST, new ArrayList<>(suggestions));
}
+ if (suggestionV2 != null) {
+ outState.putParcelableArrayList(STATE_SUGGESTION_LIST_V2,
+ new ArrayList<>(suggestionV2));
+ }
if (category != null) {
outState.putParcelable(STATE_CATEGORY_LIST, category);
}
diff --git a/src/com/android/settings/dashboard/DashboardData.java b/src/com/android/settings/dashboard/DashboardData.java
index 414757e..a14b8c2 100644
--- a/src/com/android/settings/dashboard/DashboardData.java
+++ b/src/com/android/settings/dashboard/DashboardData.java
@@ -17,6 +17,7 @@
import android.annotation.IntDef;
import android.graphics.drawable.Icon;
+import android.service.settings.suggestions.Suggestion;
import android.support.annotation.VisibleForTesting;
import android.support.v7.util.DiffUtil;
import android.text.TextUtils;
@@ -69,6 +70,7 @@
private final DashboardCategory mCategory;
private final List<Condition> mConditions;
private final List<Tile> mSuggestions;
+ private final List<Suggestion> mSuggestionsV2;
@HeaderMode
private final int mSuggestionConditionMode;
@@ -76,6 +78,7 @@
mCategory = builder.mCategory;
mConditions = builder.mConditions;
mSuggestions = builder.mSuggestions;
+ mSuggestionsV2 = builder.mSuggestionsV2;
mSuggestionConditionMode = builder.mSuggestionConditionMode;
mItems = new ArrayList<>();
@@ -124,6 +127,10 @@
return mSuggestions;
}
+ public List<Suggestion> getSuggestionsV2() {
+ return mSuggestionsV2;
+ }
+
public int getSuggestionConditionMode() {
return mSuggestionConditionMode;
}
@@ -190,13 +197,22 @@
* and mIsShowingAll, mSuggestionConditionMode flag.
*/
private void buildItemsData() {
- final boolean hasSuggestions = sizeOf(mSuggestions) > 0;
+ final boolean useSuggestionV2 = mSuggestionsV2 != null;
+ final boolean hasSuggestions = useSuggestionV2
+ ? sizeOf(mSuggestionsV2) > 0
+ : sizeOf(mSuggestions) > 0;
final List<Condition> conditions = getConditionsToShow(mConditions);
final boolean hasConditions = sizeOf(conditions) > 0;
final List<Tile> suggestions = getSuggestionsToShow(mSuggestions);
- final int hiddenSuggestion =
- hasSuggestions ? sizeOf(mSuggestions) - sizeOf(suggestions) : 0;
+ final List<Suggestion> suggestionsV2 = getSuggestionsV2ToShow(mSuggestionsV2);
+
+ final int hiddenSuggestion;
+ if (useSuggestionV2) {
+ hiddenSuggestion = hasSuggestions ? sizeOf(mSuggestionsV2) - sizeOf(suggestionsV2) : 0;
+ } else {
+ hiddenSuggestion = hasSuggestions ? sizeOf(mSuggestions) - sizeOf(suggestions) : 0;
+ }
final boolean hasSuggestionAndCollapsed = hasSuggestions
&& mSuggestionConditionMode == HEADER_MODE_COLLAPSED;
@@ -215,10 +231,15 @@
R.layout.suggestion_condition_header,
STABLE_ID_SUGGESTION_CONDITION_MIDDLE_HEADER, onlyHasConditionAndCollapsed);
- /* Suggestion container. This is the card view that contains the list of suggestions.
- * This will be added whenever the suggestion list is not empty */
- addToItemList(suggestions, R.layout.suggestion_condition_container,
- STABLE_ID_SUGGESTION_CONTAINER, sizeOf(suggestions) > 0);
+ if (useSuggestionV2) {
+ addToItemList(suggestionsV2, R.layout.suggestion_condition_container,
+ STABLE_ID_SUGGESTION_CONTAINER, sizeOf(suggestionsV2) > 0);
+ } else {
+ /* Suggestion container. This is the card view that contains the list of suggestions.
+ * This will be added whenever the suggestion list is not empty */
+ addToItemList(suggestions, R.layout.suggestion_condition_container,
+ STABLE_ID_SUGGESTION_CONTAINER, sizeOf(suggestions) > 0);
+ }
/* Second suggestion/condition header. This will be added when there is at least one
* suggestion or condition that is not currently displayed, and the user can expand the
@@ -246,7 +267,7 @@
&& !hasConditions
&& hiddenSuggestion == 0);
- if(mCategory != null) {
+ if (mCategory != null) {
for (int j = 0; j < mCategory.tiles.size(); j++) {
final Tile tile = mCategory.tiles.get(j);
addToItemList(tile, R.layout.dashboard_tile, Objects.hash(tile.title),
@@ -274,6 +295,10 @@
return result;
}
+ /**
+ * @deprecated in favor of {@link #getSuggestionsV2ToShow}.
+ */
+ @Deprecated
private List<Tile> getSuggestionsToShow(List<Tile> suggestions) {
if (suggestions == null || mSuggestionConditionMode == HEADER_MODE_COLLAPSED) {
return null;
@@ -285,6 +310,17 @@
return suggestions.subList(0, DEFAULT_SUGGESTION_COUNT);
}
+ private List<Suggestion> getSuggestionsV2ToShow(List<Suggestion> suggestions) {
+ if (suggestions == null || mSuggestionConditionMode == HEADER_MODE_COLLAPSED) {
+ return null;
+ }
+ if (mSuggestionConditionMode != HEADER_MODE_DEFAULT
+ || suggestions.size() <= DEFAULT_SUGGESTION_COUNT) {
+ return suggestions;
+ }
+ return suggestions.subList(0, DEFAULT_SUGGESTION_COUNT);
+ }
+
/**
* Builder used to build the ItemsData
* <p>
@@ -296,7 +332,12 @@
private DashboardCategory mCategory;
private List<Condition> mConditions;
+ /**
+ * @deprecated in favor of SuggestionList
+ */
+ @Deprecated
private List<Tile> mSuggestions;
+ private List<Suggestion> mSuggestionsV2;
public Builder() {
}
@@ -305,6 +346,7 @@
mCategory = dashboardData.mCategory;
mConditions = dashboardData.mConditions;
mSuggestions = dashboardData.mSuggestions;
+ mSuggestionsV2 = dashboardData.mSuggestionsV2;
mSuggestionConditionMode = dashboardData.mSuggestionConditionMode;
}
@@ -318,11 +360,20 @@
return this;
}
+ /**
+ * @deprecated in favor of {@link #setSuggestionsV2(List)})}
+ */
+ @Deprecated
public Builder setSuggestions(List<Tile> suggestions) {
this.mSuggestions = suggestions;
return this;
}
+ public Builder setSuggestionsV2(List<Suggestion> suggestions) {
+ this.mSuggestionsV2 = suggestions;
+ return this;
+ }
+
public Builder setSuggestionConditionMode(@HeaderMode int mode) {
this.mSuggestionConditionMode = mode;
return this;
diff --git a/src/com/android/settings/dashboard/DashboardSummary.java b/src/com/android/settings/dashboard/DashboardSummary.java
index 6caf052..769a3d7 100644
--- a/src/com/android/settings/dashboard/DashboardSummary.java
+++ b/src/com/android/settings/dashboard/DashboardSummary.java
@@ -21,6 +21,7 @@
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
+import android.service.settings.suggestions.Suggestion;
import android.support.annotation.VisibleForTesting;
import android.support.v7.widget.LinearLayoutManager;
import android.util.Log;
@@ -36,9 +37,9 @@
import com.android.settings.dashboard.conditional.ConditionManager.ConditionListener;
import com.android.settings.dashboard.conditional.FocusRecyclerView;
import com.android.settings.dashboard.conditional.FocusRecyclerView.FocusListener;
+import com.android.settings.dashboard.suggestions.SuggestionControllerMixin;
import com.android.settings.dashboard.suggestions.SuggestionDismissController;
import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
-import com.android.settings.dashboard.suggestions.SuggestionControllerMixin;
import com.android.settings.dashboard.suggestions.SuggestionsChecks;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.widget.ActionBarShadowController;
@@ -55,7 +56,8 @@
public class DashboardSummary extends InstrumentedFragment
implements CategoryListener, ConditionListener,
- FocusListener, SuggestionDismissController.Callback {
+ FocusListener, SuggestionDismissController.Callback,
+ SuggestionControllerMixin.SuggestionControllerHost {
public static final boolean DEBUG = false;
private static final boolean DEBUG_TIMING = false;
private static final int MAX_WAIT_MILLIS = 700;
@@ -86,7 +88,12 @@
@Override
public void onAttach(Context context) {
super.onAttach(context);
- mSuggestionControllerMixin = new SuggestionControllerMixin(context, getLifecycle());
+ mSuggestionFeatureProvider = FeatureFactory.getFactory(context)
+ .getSuggestionFeatureProvider(context);
+ if (mSuggestionFeatureProvider.isSuggestionV2Enabled(context)) {
+ mSuggestionControllerMixin = new SuggestionControllerMixin(context, this /* host */,
+ getLifecycle());
+ }
}
@Override
@@ -96,8 +103,6 @@
final Activity activity = getActivity();
mDashboardFeatureProvider = FeatureFactory.getFactory(activity)
.getDashboardFeatureProvider(activity);
- mSuggestionFeatureProvider = FeatureFactory.getFactory(activity)
- .getSuggestionFeatureProvider(activity);
mSummaryLoader = new SummaryLoader(activity, CategoryKey.CATEGORY_HOMEPAGE);
@@ -109,8 +114,7 @@
mSuggestionsChecks = new SuggestionsChecks(getContext());
}
if (DEBUG_TIMING) {
- Log.d(TAG, "onCreate took " + (System.currentTimeMillis() - startTime)
- + " ms");
+ Log.d(TAG, "onCreate took " + (System.currentTimeMillis() - startTime) + " ms");
}
}
@@ -221,12 +225,12 @@
void rebuildUI() {
if (!mSuggestionFeatureProvider.isSuggestionEnabled(getContext())) {
Log.d(TAG, "Suggestion feature is disabled, skipping suggestion entirely");
- updateCategoryAndSuggestion(null /* tiles */);
+ updateCategory();
} else {
new SuggestionLoader().execute();
// Set categories on their own if loading suggestions takes too long.
mHandler.postDelayed(() -> {
- updateCategoryAndSuggestion(null /* tiles */);
+ updateCategory();
}, MAX_WAIT_MILLIS);
}
}
@@ -264,6 +268,11 @@
}
@Override
+ public Suggestion getSuggestionAt(int position) {
+ return mAdapter.getSuggestionV2(position);
+ }
+
+ @Override
public Tile getSuggestionForPosition(int position) {
return mAdapter.getSuggestion(position);
}
@@ -273,6 +282,20 @@
mAdapter.onSuggestionDismissed(suggestion);
}
+ @Override
+ public void onSuggestionDismissed(Suggestion suggestion) {
+ mAdapter.onSuggestionDismissed(suggestion);
+ }
+
+ @Override
+ public void onSuggestionReady(List<Suggestion> suggestions) {
+ mAdapter.setSuggestionsV2(suggestions);
+ }
+
+ /**
+ * @deprecated in favor of the real SuggestionLoader.
+ */
+ @Deprecated
private class SuggestionLoader extends AsyncTask<Void, Void, List<Tile>> {
@Override
protected List<Tile> doInBackground(Void... params) {
@@ -311,6 +334,17 @@
}
}
+ void updateCategory() {
+ final DashboardCategory category = mDashboardFeatureProvider.getTilesForCategory(
+ CategoryKey.CATEGORY_HOMEPAGE);
+ mSummaryLoader.updateSummaryToCache(category);
+ mAdapter.setCategory(category);
+ }
+
+ /**
+ * @deprecated in favor of SuggestionControllerMixin.
+ */
+ @Deprecated
@VisibleForTesting
void updateCategoryAndSuggestion(List<Tile> suggestions) {
final Activity activity = getActivity();
diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionAdapter.java b/src/com/android/settings/dashboard/suggestions/SuggestionAdapter.java
index 3815211..38b9d28 100644
--- a/src/com/android/settings/dashboard/suggestions/SuggestionAdapter.java
+++ b/src/com/android/settings/dashboard/suggestions/SuggestionAdapter.java
@@ -15,13 +15,17 @@
*/
package com.android.settings.dashboard.suggestions;
+import android.app.PendingIntent;
import android.content.Context;
+import android.service.settings.suggestions.Suggestion;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
+import android.util.Log;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
@@ -30,7 +34,6 @@
import com.android.settings.dashboard.DashboardAdapter.IconCache;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.drawer.Tile;
-import com.android.settingslib.drawer.TileUtils;
import java.util.List;
import java.util.Objects;
@@ -41,14 +44,18 @@
private final Context mContext;
private final MetricsFeatureProvider mMetricsFeatureProvider;
private final SuggestionFeatureProvider mSuggestionFeatureProvider;
- private List<Tile> mSuggestions;
+ @Deprecated // in favor of mNewSuggestions
+ private final List<Tile> mSuggestions;
+ private final List<Suggestion> mSuggestionsV2;
private final IconCache mCache;
private final List<String> mSuggestionsShownLogged;
public SuggestionAdapter(Context context, List<Tile> suggestions,
+ List<Suggestion> suggestionsV2,
List<String> suggestionsShownLogged) {
mContext = context;
mSuggestions = suggestions;
+ mSuggestionsV2 = suggestionsV2;
mSuggestionsShownLogged = suggestionsShownLogged;
mCache = new IconCache(context);
final FeatureFactory factory = FeatureFactory.getFactory(context);
@@ -66,6 +73,68 @@
@Override
public void onBindViewHolder(DashboardItemHolder holder, int position) {
+ if (mSuggestions != null) {
+ bindSuggestionTile(holder, position);
+ } else {
+ bindSuggestion(holder, position);
+ }
+ }
+
+ private void bindSuggestion(DashboardItemHolder holder, int position) {
+ final Suggestion suggestion = mSuggestionsV2.get(position);
+ final String id = suggestion.getId();
+ if (!mSuggestionsShownLogged.contains(id)) {
+ mMetricsFeatureProvider.action(
+ mContext, MetricsEvent.ACTION_SHOW_SETTINGS_SUGGESTION, id);
+ mSuggestionsShownLogged.add(id);
+ }
+ // TODO: Add remote view field in Suggestion, and enable this.
+ // if (suggestion.remoteViews != null) {
+ // final ViewGroup itemView = (ViewGroup) holder.itemView;
+ // itemView.removeAllViews();
+ // itemView.addView(suggestion.remoteViews.apply(itemView.getContext(),
+ // itemView));
+ // } else
+ {
+ // TODO: Add icon field in Suggestion, and enable this.
+ // holder.icon.setImageDrawable(mCache.getIcon(suggestion.icon));
+ holder.title.setText(suggestion.getTitle());
+ final CharSequence summary = suggestion.getSummary();
+ if (!TextUtils.isEmpty(summary)) {
+ holder.summary.setText(summary);
+ holder.summary.setVisibility(View.VISIBLE);
+ } else {
+ holder.summary.setVisibility(View.GONE);
+ }
+ }
+ final View divider = holder.itemView.findViewById(R.id.divider);
+ if (divider != null) {
+ divider.setVisibility(position < mSuggestionsV2.size() - 1 ? View.VISIBLE : View.GONE);
+ }
+ View clickHandler = holder.itemView;
+ // If a view with @android:id/primary is defined, use that as the click handler
+ // instead.
+ final View primaryAction = holder.itemView.findViewById(android.R.id.primary);
+ if (primaryAction != null) {
+ clickHandler = primaryAction;
+ // set the item view to disabled to remove any touch effects
+ holder.itemView.setEnabled(false);
+ }
+ clickHandler.setOnClickListener(v -> {
+ mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_SETTINGS_SUGGESTION, id);
+ try {
+ suggestion.getPendingIntent().send();
+ } catch (PendingIntent.CanceledException e) {
+ Log.w(TAG, "Failed to start suggestion " + suggestion.getTitle());
+ }
+ });
+ }
+
+ /**
+ * @deprecated in favor {@link #bindSuggestion(DashboardItemHolder, int)}.
+ */
+ @Deprecated
+ private void bindSuggestionTile(DashboardItemHolder holder, int position) {
final Tile suggestion = (Tile) mSuggestions.get(position);
final String suggestionId = mSuggestionFeatureProvider.getSuggestionIdentifier(
mContext, suggestion);
@@ -77,7 +146,6 @@
mSuggestionsShownLogged.add(suggestionId);
}
if (suggestion.remoteViews != null) {
- TileUtils.updateTileUsingSummaryUri(mContext, suggestion);
final ViewGroup itemView = (ViewGroup) holder.itemView;
itemView.removeAllViews();
itemView.addView(suggestion.remoteViews.apply(itemView.getContext(), itemView));
@@ -115,24 +183,46 @@
@Override
public long getItemId(int position) {
- return Objects.hash(mSuggestions.get(position).title);
+ if (mSuggestions != null) {
+ return Objects.hash(mSuggestions.get(position).title);
+ } else {
+ return Objects.hash(mSuggestionsV2.get(position).getId());
+ }
}
@Override
public int getItemViewType(int position) {
- Tile suggestion = getSuggestion(position);
- return suggestion.remoteViews != null
- ? R.layout.suggestion_tile_remote_container
- : R.layout.suggestion_tile;
+ if (mSuggestions != null) {
+ Tile suggestion = getSuggestion(position);
+
+ return suggestion.remoteViews != null
+ ? R.layout.suggestion_tile_remote_container
+ : R.layout.suggestion_tile;
+ } else {
+
+ return R.layout.suggestion_tile;
+ // TODO: Add remote view field in Suggestion, and enable this.
+ // Suggestion suggestion = getSuggestionsV2(position);
+ // return suggestion.remoteViews != null
+ // ? R.layout.suggestion_tile_remote_container
+ // : R.layout.suggestion_tile;
+ }
}
@Override
public int getItemCount() {
- return mSuggestions.size();
+ if (mSuggestions != null) {
+ return mSuggestions.size();
+ } else {
+ return mSuggestionsV2.size();
+ }
}
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;
@@ -141,6 +231,19 @@
return null;
}
+ 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;
+ }
+ }
+ return null;
+ }
+
public void removeSuggestion(Tile suggestion) {
mSuggestions.remove(suggestion);
notifyDataSetChanged();
@@ -151,4 +254,8 @@
mSuggestionFeatureProvider.isSmartSuggestionEnabled(mContext));
}
+ public void removeSuggestion(Suggestion suggestion) {
+ mSuggestionsV2.remove(suggestion);
+ notifyDataSetChanged();
+ }
}
diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixin.java b/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixin.java
index 6f5c82e..21ae435 100644
--- a/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixin.java
+++ b/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixin.java
@@ -16,11 +16,12 @@
package com.android.settings.dashboard.suggestions;
+import android.app.LoaderManager;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Loader;
+import android.os.Bundle;
import android.service.settings.suggestions.Suggestion;
-import android.support.annotation.VisibleForTesting;
-import android.util.FeatureFlagUtils;
import android.util.Log;
import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -34,23 +35,32 @@
* Manages IPC communication to SettingsIntelligence for suggestion related services.
*/
public class SuggestionControllerMixin implements SuggestionController.ServiceConnectionListener,
- LifecycleObserver, OnStart, OnStop {
+ LifecycleObserver, OnStart, OnStop, LoaderManager.LoaderCallbacks<List<Suggestion>> {
- @VisibleForTesting
- static final String FEATURE_FLAG = "new_settings_suggestion";
+ public interface SuggestionControllerHost {
+ /**
+ * Called when suggestion data fetching is ready.
+ */
+ void onSuggestionReady(List<Suggestion> data);
+
+ /**
+ * Returns {@link LoaderManager} associated with the host.
+ */
+ LoaderManager getLoaderManager();
+ }
+
private static final String TAG = "SuggestionCtrlMixin";
private static final boolean DEBUG = false;
private final Context mContext;
private final SuggestionController mSuggestionController;
+ private final SuggestionControllerHost mHost;
- public static boolean isEnabled() {
- return FeatureFlagUtils.isEnabled(FEATURE_FLAG);
- }
-
- public SuggestionControllerMixin(Context context, Lifecycle lifecycle) {
+ public SuggestionControllerMixin(Context context, SuggestionControllerHost host,
+ Lifecycle lifecycle) {
mContext = context.getApplicationContext();
- mSuggestionController = new SuggestionController(context,
+ mHost = host;
+ mSuggestionController = new SuggestionController(mContext,
new ComponentName(
"com.android.settings.intelligence",
"com.android.settings.intelligence.suggestions.SuggestionService"),
@@ -62,10 +72,6 @@
@Override
public void onStart() {
- if (!isEnabled()) {
- Log.w(TAG, "Feature not enabled, skipping");
- return;
- }
mSuggestionController.start();
}
@@ -76,11 +82,8 @@
@Override
public void onServiceConnected() {
- // TODO: Call API to get data from a loader instead of in current thread.
- final List<Suggestion> data = mSuggestionController.getSuggestions();
- if (DEBUG) {
- Log.d(TAG, "data size " + (data == null ? 0 : data.size()));
- }
+ mHost.getLoaderManager().restartLoader(SuggestionLoader.LOADER_ID_SUGGESTIONS,
+ null /* args */, this /* callback */);
}
@Override
@@ -88,5 +91,24 @@
if (DEBUG) {
Log.d(TAG, "SuggestionService disconnected");
}
+ mHost.getLoaderManager().destroyLoader(SuggestionLoader.LOADER_ID_SUGGESTIONS);
+ }
+
+ @Override
+ public Loader<List<Suggestion>> onCreateLoader(int id, Bundle args) {
+ if (id == SuggestionLoader.LOADER_ID_SUGGESTIONS) {
+ return new SuggestionLoader(mContext, mSuggestionController);
+ }
+ throw new IllegalArgumentException("This loader id is not supported " + id);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<List<Suggestion>> loader, List<Suggestion> data) {
+ mHost.onSuggestionReady(data);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<List<Suggestion>> loader) {
+
}
}
diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionDismissController.java b/src/com/android/settings/dashboard/suggestions/SuggestionDismissController.java
index 100c955..6affa8c 100644
--- a/src/com/android/settings/dashboard/suggestions/SuggestionDismissController.java
+++ b/src/com/android/settings/dashboard/suggestions/SuggestionDismissController.java
@@ -17,6 +17,7 @@
package com.android.settings.dashboard.suggestions;
import android.content.Context;
+import android.service.settings.suggestions.Suggestion;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
@@ -30,18 +31,37 @@
public interface Callback {
/**
+ * @deprecated in favor of {@link #getSuggestionAt(int)}
* Returns suggestion tile data from the callback
*/
+ @Deprecated
Tile getSuggestionForPosition(int position);
/**
+ * @deprecated in favor of {@link #onSuggestionDismissed(Suggestion)}
+ * Called when a suggestion is dismissed.
+ */
+ @Deprecated
+ void onSuggestionDismissed(Tile suggestion);
+
+ /**
+ * Returns suggestion tile data from the callback
+ */
+ Suggestion getSuggestionAt(int position);
+
+ /**
* Called when a suggestion is dismissed.
*/
- void onSuggestionDismissed(Tile suggestion);
+ void onSuggestionDismissed(Suggestion suggestion);
}
private final Context mContext;
private final SuggestionFeatureProvider mSuggestionFeatureProvider;
+
+ /**
+ * @deprecated in favor of the new Suggestion backend.
+ */
+ @Deprecated
private final SuggestionParser mSuggestionParser;
private final Callback mCallback;
@@ -79,8 +99,15 @@
if (mCallback == null) {
return;
}
- final Tile suggestion = mCallback.getSuggestionForPosition(viewHolder.getAdapterPosition());
- mSuggestionFeatureProvider.dismissSuggestion(mContext, mSuggestionParser, suggestion);
- mCallback.onSuggestionDismissed(suggestion);
+ final int position = viewHolder.getAdapterPosition();
+ final Suggestion suggestionV2 = mCallback.getSuggestionAt(position);
+ if (suggestionV2 != null) {
+ mSuggestionFeatureProvider.dismissSuggestion(mContext, suggestionV2);
+ mCallback.onSuggestionDismissed(suggestionV2);
+ } else {
+ final Tile suggestion = mCallback.getSuggestionForPosition(position);
+ mSuggestionFeatureProvider.dismissSuggestion(mContext, mSuggestionParser, suggestion);
+ mCallback.onSuggestionDismissed(suggestion);
+ }
}
}
diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProvider.java b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProvider.java
index d19a778..b649572 100644
--- a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProvider.java
+++ b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProvider.java
@@ -19,6 +19,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
+import android.service.settings.suggestions.Suggestion;
import android.support.annotation.NonNull;
import com.android.settingslib.drawer.Tile;
@@ -31,10 +32,17 @@
/**
* Whether or not the whole suggestion feature is enabled.
+ * @deprecated in favor of {@link #isSuggestionV2Enabled(Context)}
*/
+ @Deprecated
boolean isSuggestionEnabled(Context context);
/**
+ * Whether or not the suggestion v2 feature is enabled.
+ */
+ boolean isSuggestionV2Enabled(Context context);
+
+ /**
* Returns true if smart suggestion should be used instead of xml based SuggestionParser.
*/
boolean isSmartSuggestionEnabled(Context context);
@@ -52,7 +60,9 @@
*
* @param suggestions List of suggestion Tiles
* @param suggestionIds List of suggestion ids corresponding to the suggestion tiles.
+ * @deprecated in favor of SettingsIntelligence
*/
+ @Deprecated
void rankSuggestions(final List<Tile> suggestions, List<String> suggestionIds);
/**
@@ -62,10 +72,18 @@
/**
* Dismisses a suggestion.
+ *
+ * @deprecated in favor of {@link #dismissSuggestion(Context, Suggestion)}
*/
+ @Deprecated
void dismissSuggestion(Context context, SuggestionParser parser, Tile suggestion);
/**
+ * Dismisses a suggestion.
+ */
+ void dismissSuggestion(Context context, Suggestion suggestion);
+
+ /**
* Returns an identifier for the suggestion
*/
String getSuggestionIdentifier(Context context, Tile suggestion);
diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java
index 3d40d96..1c61d8e 100644
--- a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java
+++ b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java
@@ -23,11 +23,14 @@
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.provider.Settings.Secure;
+import android.service.settings.suggestions.Suggestion;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
+import android.util.FeatureFlagUtils;
import android.util.Log;
import android.util.Pair;
+import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.Settings.AmbientDisplayPickupSuggestionActivity;
import com.android.settings.Settings.AmbientDisplaySuggestionActivity;
@@ -46,7 +49,6 @@
import com.android.settingslib.drawer.Tile;
import com.android.settingslib.suggestions.SuggestionParser;
-import java.util.ArrayList;
import java.util.List;
public class SuggestionFeatureProviderImpl implements SuggestionFeatureProvider {
@@ -55,6 +57,8 @@
private static final int EXCLUSIVE_SUGGESTION_MAX_COUNT = 3;
private static final String SHARED_PREF_FILENAME = "suggestions";
+ @VisibleForTesting
+ static final String FEATURE_FLAG_SUGGESTIONS_V2 = "new_settings_suggestion";
private final SuggestionRanker mSuggestionRanker;
private final MetricsFeatureProvider mMetricsFeatureProvider;
@@ -63,7 +67,20 @@
public boolean isSuggestionEnabled(Context context) {
final ActivityManager am =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
- return !am.isLowRamDevice();
+ boolean isLowRamDevice = am.isLowRamDevice();
+ return !isLowRamDevice && !isV2Enabled();
+ }
+
+ @Override
+ public boolean isSuggestionV2Enabled(Context context) {
+ final ActivityManager am =
+ (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ boolean isLowRamDevice = am.isLowRamDevice();
+ return !isLowRamDevice && isV2Enabled();
+ }
+
+ private static boolean isV2Enabled() {
+ return FeatureFlagUtils.isEnabled(FEATURE_FLAG_SUGGESTIONS_V2);
}
@Override
@@ -149,6 +166,17 @@
}
@Override
+ public void dismissSuggestion(Context context, Suggestion suggestion) {
+ if (suggestion == null || context == null) {
+ return;
+ }
+ mMetricsFeatureProvider.action(
+ context, MetricsProto.MetricsEvent.ACTION_SETTINGS_DISMISS_SUGGESTION,
+ suggestion.getId());
+ // TODO: Call SettingsIntelligence to dismiss suggestion.
+ }
+
+ @Override
public String getSuggestionIdentifier(Context context, Tile suggestion) {
if (suggestion.intent == null || suggestion.intent.getComponent() == null
|| context == null) {
@@ -166,8 +194,7 @@
@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/dashboard/suggestions/SuggestionLoader.java b/src/com/android/settings/dashboard/suggestions/SuggestionLoader.java
new file mode 100644
index 0000000..b9d51ce
--- /dev/null
+++ b/src/com/android/settings/dashboard/suggestions/SuggestionLoader.java
@@ -0,0 +1,54 @@
+/*
+ * 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.dashboard.suggestions;
+
+import android.content.Context;
+import android.service.settings.suggestions.Suggestion;
+import android.util.Log;
+
+import com.android.settings.utils.AsyncLoader;
+
+import java.util.List;
+
+public class SuggestionLoader extends AsyncLoader<List<Suggestion>> {
+
+ public static final int LOADER_ID_SUGGESTIONS = 42;
+ private static final String TAG = "SuggestionLoader";
+
+ private final SuggestionController mSuggestionController;
+
+ public SuggestionLoader(Context context, SuggestionController controller) {
+ super(context);
+ mSuggestionController = controller;
+ }
+
+ @Override
+ protected void onDiscardResult(List<Suggestion> result) {
+
+ }
+
+ @Override
+ public List<Suggestion> loadInBackground() {
+ final List<Suggestion> data = mSuggestionController.getSuggestions();
+ if (data == null) {
+ Log.d(TAG, "data is null");
+ } else {
+ Log.d(TAG, "data size " + data.size());
+ }
+ return data;
+ }
+}
diff --git a/src/com/android/settings/development/BluetoothSnoopLogPreferenceController.java b/src/com/android/settings/development/BluetoothSnoopLogPreferenceController.java
index 7734e93..44a2ecd 100644
--- a/src/com/android/settings/development/BluetoothSnoopLogPreferenceController.java
+++ b/src/com/android/settings/development/BluetoothSnoopLogPreferenceController.java
@@ -69,12 +69,12 @@
}
@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 e0b0837..20af2c6 100644
--- a/src/com/android/settings/development/ColorModePreference.java
+++ b/src/com/android/settings/development/ColorModePreference.java
@@ -28,6 +28,7 @@
import com.android.settings.R;
import java.util.ArrayList;
+import java.util.List;
public class ColorModePreference extends SwitchPreference implements DisplayListener {
@@ -35,7 +36,28 @@
private Display mDisplay;
private int mCurrentIndex;
- private ArrayList<ColorModeDescription> mDescriptions;
+ private List<ColorModeDescription> mDescriptions;
+
+ public static List<ColorModeDescription> getColorModeDescriptions(Context context) {
+
+ List<ColorModeDescription> 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 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 a7d45eb..2f1f254 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 @@
}
/**
- * 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 0000000..54d1fa3
--- /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 8df25c2..d334bd0 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.List;
public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFragment
- implements SwitchBar.OnSwitchChangeListener {
+ implements SwitchBar.OnSwitchChangeListener, OemUnlockDialogHost {
private static final String TAG = "DevSettingsDashboard";
@@ -104,6 +107,33 @@
}
@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 @@
@Override
protected List<AbstractPreferenceController> getPreferenceControllers(Context context) {
- mPreferenceControllers = buildPreferenceControllers(context, getLifecycle());
+ mPreferenceControllers = buildPreferenceControllers(context, getActivity(), getLifecycle(),
+ this /* devOptionsDashboardFragment */);
return mPreferenceControllers;
}
@@ -140,14 +171,92 @@
}
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
- Lifecycle lifecycle) {
+ Activity activity, Lifecycle lifecycle, DevelopmentSettingsDashboardFragment fragment) {
final List<AbstractPreferenceController> 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 extends AbstractPreferenceController> T getDevelopmentOptionsController(Class<T> clazz) {
+ return getPreferenceController(clazz);
+ }
+
/**
* For Search.
*/
@@ -171,7 +280,8 @@
@Override
public List<AbstractPreferenceController> 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 0000000..2486ef5
--- /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 0000000..c134e9c
--- /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 0000000..cb391a8
--- /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 0000000..fe4755f
--- /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 a590d7d..ecbb9d0 100644
--- a/src/com/android/settings/development/StayAwakePreferenceController.java
+++ b/src/com/android/settings/development/StayAwakePreferenceController.java
@@ -101,19 +101,6 @@
}
@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) {
mSettingsObserver.register(true /* register */);
@@ -127,6 +114,19 @@
}
}
+ @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 38b57a2..b966530 100644
--- a/src/com/android/settings/display/NightDisplayPreference.java
+++ b/src/com/android/settings/display/NightDisplayPreference.java
@@ -22,6 +22,7 @@
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 @@
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 @@
}
@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 23ddf07..f9568fb 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.SettingsPreferenceFragment;
import java.text.DateFormat;
+import java.time.LocalTime;
import java.util.Calendar;
import java.util.TimeZone;
@@ -144,7 +145,7 @@
@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 @@
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 @@
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 @@
}
@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 e9f8caf..5db256d 100644
--- a/src/com/android/settings/notification/ChannelNotificationSettings.java
+++ b/src/com/android/settings/notification/ChannelNotificationSettings.java
@@ -360,7 +360,9 @@
}
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 @@
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/SavedAccessPointsWifiSettings.java b/src/com/android/settings/wifi/SavedAccessPointsWifiSettings.java
index 0e4c542..4838cc6 100644
--- a/src/com/android/settings/wifi/SavedAccessPointsWifiSettings.java
+++ b/src/com/android/settings/wifi/SavedAccessPointsWifiSettings.java
@@ -116,6 +116,7 @@
}
private void initPreferences() {
+ Log.d(TAG, "Rebuilding the preferences");
PreferenceScreen preferenceScreen = getPreferenceScreen();
final Context context = getPrefContext();
diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java
index b47391d..b143f58 100644
--- a/src/com/android/settings/wifi/WifiSettings.java
+++ b/src/com/android/settings/wifi/WifiSettings.java
@@ -1070,7 +1070,7 @@
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/tests/robotests/src/android/service/settings/suggestions/Suggestion.java b/tests/robotests/src/android/service/settings/suggestions/Suggestion.java
index df7a8ae..2bb6192 100644
--- a/tests/robotests/src/android/service/settings/suggestions/Suggestion.java
+++ b/tests/robotests/src/android/service/settings/suggestions/Suggestion.java
@@ -16,5 +16,104 @@
package android.service.settings.suggestions;
+import android.app.PendingIntent;
+import android.os.Parcel;
+import android.text.TextUtils;
+
public class Suggestion {
+ private final String mId;
+ private final CharSequence mTitle;
+ private final CharSequence mSummary;
+ private final PendingIntent mPendingIntent;
+
+ /**
+ * Gets the id for the suggestion object.
+ */
+ public String getId() {
+ return mId;
+ }
+
+ /**
+ * Title of the suggestion that is shown to the user.
+ */
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Optional summary describing what this suggestion controls.
+ */
+ public CharSequence getSummary() {
+ return mSummary;
+ }
+
+ /**
+ * The Intent to launch when the suggestion is activated.
+ */
+ public PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
+
+ private Suggestion(Builder builder) {
+ mTitle = builder.mTitle;
+ mSummary = builder.mSummary;
+ mPendingIntent = builder.mPendingIntent;
+ mId = builder.mId;
+ }
+
+ private Suggestion(Parcel in) {
+ mId = in.readString();
+ mTitle = in.readCharSequence();
+ mSummary = in.readCharSequence();
+ mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader());
+ }
+
+ /**
+ * Builder class for {@link Suggestion}.
+ */
+ public static class Builder {
+ private final String mId;
+ private CharSequence mTitle;
+ private CharSequence mSummary;
+ private PendingIntent mPendingIntent;
+
+ public Builder(String id) {
+ if (TextUtils.isEmpty(id)) {
+ throw new IllegalArgumentException("Suggestion id cannot be empty");
+ }
+ mId = id;
+ }
+
+ /**
+ * Sets suggestion title
+ */
+
+ public Builder setTitle(CharSequence title) {
+ mTitle = title;
+ return this;
+ }
+
+ /**
+ * Sets suggestion summary
+ */
+ public Builder setSummary(CharSequence summary) {
+ mSummary = summary;
+ return this;
+ }
+
+ /**
+ * Sets suggestion intent
+ */
+ public Builder setPendingIntent(PendingIntent pendingIntent) {
+ mPendingIntent = pendingIntent;
+ return this;
+ }
+
+ /**
+ * Builds an immutable {@link Suggestion} object.
+ */
+ public Suggestion build() {
+ return new Suggestion(this /* builder */);
+ }
+ }
}
diff --git a/tests/robotests/src/com/android/settings/applications/UsageAccessDetailsTest.java b/tests/robotests/src/com/android/settings/applications/UsageAccessDetailsTest.java
index 229057f..07acb13 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.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 @@
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 e3f00d8..0c27412 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 @@
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.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 @@
@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<BluetoothDevice> 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 @@
@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 @@
@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,9 +138,11 @@
@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(
@@ -126,58 +150,71 @@
}
@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<CachedBluetoothDevice> 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<CachedBluetoothDevice> 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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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<BluetoothDevice> 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 ea33c83..ff91c40 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 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.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 mLogWriter;
+ @Mock private LogWriter mockLogWriter;
+ @Mock private VisibilityLoggerMixin mockVisibilityLogger;
+
private Context mContext;
private MetricsFeatureProvider mProvider;
+ @Captor
+ private ArgumentCaptor<Pair> mPairCaptor;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mProvider = new MetricsFeatureProvider();
List<LogWriter> 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 @@
mProvider.logDashboardStartIntent(mContext, null /* intent */,
MetricsEvent.SETTINGS_GESTURES);
- verifyNoMoreInteractions(mLogWriter);
+ verifyNoMoreInteractions(mockLogWriter);
}
@Test
@@ -86,7 +99,7 @@
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 @@
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/DashboardAdapterTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java
index 4b345d0..b14a80f 100644
--- a/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java
@@ -28,6 +28,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -35,6 +36,7 @@
import android.content.res.TypedArray;
import android.graphics.drawable.Icon;
import android.os.Bundle;
+import android.service.settings.suggestions.Suggestion;
import android.support.v7.widget.RecyclerView;
import android.util.DisplayMetrics;
import android.util.Pair;
@@ -558,6 +560,33 @@
}
@Test
+ public void testBindConditionAndSuggestion_v2_shouldSetSuggestionAdapterAndNoCrash() {
+ mDashboardAdapter = new DashboardAdapter(mContext, null, null, null, null);
+ final List<Suggestion> suggestions = makeSuggestionsV2("pkg1");
+ final DashboardCategory category = mock(DashboardCategory.class);
+ final List<Tile> tiles = new ArrayList<>();
+ tiles.add(mock(Tile.class));
+ category.tiles = tiles;
+
+ mDashboardAdapter.setSuggestionsV2(suggestions);
+
+ final RecyclerView data = mock(RecyclerView.class);
+ when(data.getResources()).thenReturn(mResources);
+ when(data.getContext()).thenReturn(mContext);
+ when(mResources.getDisplayMetrics()).thenReturn(mock(DisplayMetrics.class));
+ final View itemView = mock(View.class);
+ when(itemView.findViewById(R.id.data)).thenReturn(data);
+ final DashboardAdapter.SuggestionAndConditionContainerHolder holder =
+ new DashboardAdapter.SuggestionAndConditionContainerHolder(itemView);
+
+ mDashboardAdapter.onBindConditionAndSuggestion(
+ holder, DashboardAdapter.SUGGESTION_CONDITION_HEADER_POSITION);
+
+ verify(data).setAdapter(any(SuggestionAdapter.class));
+ // should not crash
+ }
+
+ @Test
public void testBindConditionAndSuggestion_emptySuggestion_shouldSetConditionAdpater() {
final Bundle savedInstance = new Bundle();
savedInstance.putInt(DashboardAdapter.STATE_SUGGESTION_CONDITION_MODE,
@@ -598,6 +627,17 @@
return suggestions;
}
+ private List<Suggestion> makeSuggestionsV2(String... pkgNames) {
+ final List<Suggestion> suggestions = new ArrayList<>();
+ for (String pkgName : pkgNames) {
+ final Suggestion suggestion = new Suggestion.Builder(pkgName)
+ .setPendingIntent(mock(PendingIntent.class))
+ .build();
+ suggestions.add(suggestion);
+ }
+ return suggestions;
+ }
+
private void setupSuggestions(List<Tile> suggestions) {
mDashboardAdapter.setCategoriesAndSuggestions(null /* category */, suggestions);
final Context context = RuntimeEnvironment.application;
@@ -605,4 +645,6 @@
LayoutInflater.from(context).inflate(
R.layout.suggestion_condition_header, new RelativeLayout(context), true));
}
+
+
}
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 bd3650b..6b80465 100644
--- a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterTest.java
@@ -15,13 +15,23 @@
*/
package com.android.settings.dashboard.suggestions;
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Icon;
+import android.service.settings.suggestions.Suggestion;
+import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ContextThemeWrapper;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.FrameLayout;
@@ -31,15 +41,12 @@
import com.android.settings.R;
import com.android.settings.SettingsActivity;
-import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.dashboard.DashboardAdapter;
import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settingslib.drawer.Tile;
-import java.util.ArrayList;
-import java.util.List;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -50,22 +57,13 @@
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplication;
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.verify;
+import java.util.ArrayList;
+import java.util.List;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class SuggestionAdapterTest {
- @Mock
- private Tile mSuggestion1;
- @Mock
- private Tile mSuggestion2;
+
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private SettingsActivity mActivity;
@@ -74,6 +72,8 @@
private DashboardAdapter.DashboardItemHolder mSuggestionHolder;
private List<Tile> mOneSuggestion;
private List<Tile> mTwoSuggestions;
+ private List<Suggestion> mOneSuggestionV2;
+ private List<Suggestion> mTwoSuggestionsV2;
@Before
public void setUp() {
@@ -81,39 +81,75 @@
mContext = RuntimeEnvironment.application;
FakeFeatureFactory.setupForTest(mActivity);
- mSuggestion1.title = "Test Suggestion 1";
- mSuggestion1.icon = mock(Icon.class);
- mSuggestion2.title = "Test Suggestion 2";
- mSuggestion2.icon = mock(Icon.class);
+ final Tile suggestion1 = new Tile();
+ final Tile suggestion2 = new Tile();
+ final Suggestion suggestion1V2 = new Suggestion.Builder("id1")
+ .setTitle("Test suggestion 1")
+ .build();
+ final Suggestion suggestion2V2 = new Suggestion.Builder("id2")
+ .setTitle("Test suggestion 2")
+ .build();
+ suggestion1.title = "Test Suggestion 1";
+ suggestion1.icon = mock(Icon.class);
+ suggestion2.title = "Test Suggestion 2";
+ suggestion2.icon = mock(Icon.class);
mOneSuggestion = new ArrayList<>();
- mOneSuggestion.add(mSuggestion1);
+ mOneSuggestion.add(suggestion1);
mTwoSuggestions = new ArrayList<>();
- mTwoSuggestions.add(mSuggestion1);
- mTwoSuggestions.add(mSuggestion2);
+ mTwoSuggestions.add(suggestion1);
+ mTwoSuggestions.add(suggestion2);
+ mOneSuggestionV2 = new ArrayList<>();
+ mOneSuggestionV2.add(suggestion1V2);
+ mTwoSuggestionsV2 = new ArrayList<>();
+ mTwoSuggestionsV2.add(suggestion1V2);
+ mTwoSuggestionsV2.add(suggestion2V2);
}
@Test
public void getItemCount_shouldReturnListSize() {
- mSuggestionAdapter = new SuggestionAdapter(mContext, mOneSuggestion, new ArrayList<>());
+ mSuggestionAdapter = new SuggestionAdapter(mContext, mOneSuggestion,
+ null /* suggestionV2 */, new ArrayList<>());
assertThat(mSuggestionAdapter.getItemCount()).isEqualTo(1);
- mSuggestionAdapter = new SuggestionAdapter(mContext, mTwoSuggestions, new ArrayList<>());
+ mSuggestionAdapter = new SuggestionAdapter(mContext, mTwoSuggestions,
+ null /* suggestionV2 */, new ArrayList<>());
+ assertThat(mSuggestionAdapter.getItemCount()).isEqualTo(2);
+ }
+
+ @Test
+ public void getItemCount_v2_shouldReturnListSize() {
+ mSuggestionAdapter = new SuggestionAdapter(mContext, null /* suggestions */,
+ mOneSuggestionV2, new ArrayList<>());
+ assertThat(mSuggestionAdapter.getItemCount()).isEqualTo(1);
+
+ mSuggestionAdapter = new SuggestionAdapter(mContext, null /* suggestions */,
+ mTwoSuggestionsV2, new ArrayList<>());
assertThat(mSuggestionAdapter.getItemCount()).isEqualTo(2);
}
@Test
public void getItemViewType_shouldReturnSuggestionTile() {
- mSuggestionAdapter = new SuggestionAdapter(mContext, mOneSuggestion, new ArrayList<>());
+ mSuggestionAdapter = new SuggestionAdapter(mContext, mOneSuggestion,
+ null /* suggestionV2 */, new ArrayList<>());
assertThat(mSuggestionAdapter.getItemViewType(0))
- .isEqualTo(R.layout.suggestion_tile);
+ .isEqualTo(R.layout.suggestion_tile);
+ }
+
+ @Test
+ public void getItemViewType_v2_shouldReturnSuggestionTile() {
+ mSuggestionAdapter = new SuggestionAdapter(mContext, null /* suggestions */,
+ mOneSuggestionV2, new ArrayList<>());
+ assertThat(mSuggestionAdapter.getItemViewType(0))
+ .isEqualTo(R.layout.suggestion_tile);
}
@Test
public void onBindViewHolder_shouldSetListener() {
final View view = spy(LayoutInflater.from(mContext).inflate(
- R.layout.suggestion_tile, new LinearLayout(mContext), true));
+ R.layout.suggestion_tile, new LinearLayout(mContext), true));
mSuggestionHolder = new DashboardAdapter.DashboardItemHolder(view);
- mSuggestionAdapter = new SuggestionAdapter(mContext, mOneSuggestion, new ArrayList<>());
+ mSuggestionAdapter = new SuggestionAdapter(mContext, mOneSuggestion,
+ null /* suggestionV2 */, new ArrayList<>());
mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
@@ -127,7 +163,7 @@
TextView textView = new TextView(RuntimeEnvironment.application);
doReturn(textView).when(remoteViews).apply(any(Context.class), any(ViewGroup.class));
packages.get(0).remoteViews = remoteViews;
- setupSuggestions(mActivity, packages);
+ setupSuggestions(mActivity, packages, null);
mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
@@ -150,7 +186,7 @@
layout.addView(primary);
doReturn(layout).when(remoteViews).apply(any(Context.class), any(ViewGroup.class));
packages.get(0).remoteViews = remoteViews;
- setupSuggestions(mActivity, packages);
+ setupSuggestions(mActivity, packages, null /* suggestionV2 */);
mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
mSuggestionHolder.itemView.performClick();
@@ -164,6 +200,18 @@
}
@Test
+ public void onBindViewHolder_v2_itemViewShouldHandleClick()
+ throws PendingIntent.CanceledException {
+ final List<Suggestion> packages = makeSuggestionsV2("pkg1");
+ setupSuggestions(mActivity, null /* suggestionV1 */, packages);
+
+ mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
+ mSuggestionHolder.itemView.performClick();
+
+ verify(packages.get(0).getPendingIntent()).send();
+ }
+
+ @Test
public void onBindViewHolder_viewsShouldClearOnRebind() {
Context context =
new ContextThemeWrapper(RuntimeEnvironment.application, R.style.Theme_Settings);
@@ -176,7 +224,7 @@
layout.addView(primary);
doReturn(layout).when(remoteViews).apply(any(Context.class), any(ViewGroup.class));
packages.get(0).remoteViews = remoteViews;
- setupSuggestions(mActivity, packages);
+ setupSuggestions(mActivity, packages, null /* suggestionV2 */);
mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
@@ -185,8 +233,26 @@
assertThat(itemView.getChildCount()).isEqualTo(1);
}
- private void setupSuggestions(Context context, List<Tile> suggestions) {
- mSuggestionAdapter = new SuggestionAdapter(context, suggestions, new ArrayList<>());
+ @Test
+ public void getSuggestionsV2_shouldReturnSuggestionWhenMatch() {
+ final List<Suggestion> suggestionsV2 = makeSuggestionsV2("pkg1");
+ setupSuggestions(mActivity, null /* suggestionV1 */, suggestionsV2);
+
+ assertThat(mSuggestionAdapter.getSuggestion(0)).isNull();
+ assertThat(mSuggestionAdapter.getSuggestionsV2(0)).isNotNull();
+
+ List<Tile> suggestionsV1 = makeSuggestions("pkg1");
+ setupSuggestions(mActivity, suggestionsV1, null /* suggestionV2 */);
+
+ assertThat(mSuggestionAdapter.getSuggestionsV2(0)).isNull();
+ assertThat(mSuggestionAdapter.getSuggestion(0)).isNotNull();
+
+ }
+
+ private void setupSuggestions(Context context, List<Tile> suggestions,
+ List<Suggestion> suggestionsV2) {
+ mSuggestionAdapter = new SuggestionAdapter(context, suggestions, suggestionsV2,
+ new ArrayList<>());
mSuggestionHolder = mSuggestionAdapter.onCreateViewHolder(
new FrameLayout(RuntimeEnvironment.application),
mSuggestionAdapter.getItemViewType(0));
@@ -204,4 +270,14 @@
return suggestions;
}
+ private List<Suggestion> makeSuggestionsV2(String... pkgNames) {
+ final List<Suggestion> suggestions = new ArrayList<>();
+ for (String pkgName : pkgNames) {
+ final Suggestion suggestion = new Suggestion.Builder(pkgName)
+ .setPendingIntent(mock(PendingIntent.class))
+ .build();
+ suggestions.add(suggestion);
+ }
+ return suggestions;
+ }
}
diff --git a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixinTest.java b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixinTest.java
index e118478..e42466c 100644
--- a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixinTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixinTest.java
@@ -17,14 +17,15 @@
package com.android.settings.dashboard.suggestions;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.LoaderManager;
import android.content.Context;
-import android.util.FeatureFlagUtils;
import com.android.settings.TestConfig;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
-import com.android.settings.testutils.shadow.SettingsShadowSystemProperties;
import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.After;
@@ -38,13 +39,14 @@
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
shadows = {
- SettingsShadowSystemProperties.class,
ShadowSuggestionController.class
})
public class SuggestionControllerMixinTest {
@Mock
private Context mContext;
+ @Mock
+ private SuggestionControllerMixin.SuggestionControllerHost mHost;
private Lifecycle mLifecycle;
private SuggestionControllerMixin mMixin;
@@ -53,33 +55,16 @@
MockitoAnnotations.initMocks(this);
mLifecycle = new Lifecycle();
when(mContext.getApplicationContext()).thenReturn(mContext);
- SettingsShadowSystemProperties.set(
- FeatureFlagUtils.FFLAG_PREFIX + SuggestionControllerMixin.FEATURE_FLAG, "true");
}
@After
public void tearDown() {
ShadowSuggestionController.reset();
- SettingsShadowSystemProperties.clear();
}
@Test
- public void systemPropertySet_verifyIsEnabled() {
- SettingsShadowSystemProperties.set(
- FeatureFlagUtils.FFLAG_PREFIX + SuggestionControllerMixin.FEATURE_FLAG, "true");
- assertThat(SuggestionControllerMixin.isEnabled()).isTrue();
- }
-
- @Test
- public void systemPropertyNotSet_verifyIsDisabled() {
- SettingsShadowSystemProperties.set(
- FeatureFlagUtils.FFLAG_PREFIX + SuggestionControllerMixin.FEATURE_FLAG, "false");
- assertThat(SuggestionControllerMixin.isEnabled()).isFalse();
- }
-
- @Test
- public void goThroughLifecycle_onStartStop_shouldStartStopService() {
- mMixin = new SuggestionControllerMixin(mContext, mLifecycle);
+ public void goThroughLifecycle_onStartStop_shouldStartStopController() {
+ mMixin = new SuggestionControllerMixin(mContext, mHost, mLifecycle);
mLifecycle.onStart();
assertThat(ShadowSuggestionController.sStartCalled).isTrue();
@@ -90,10 +75,13 @@
@Test
public void onServiceConnected_shouldGetSuggestion() {
- mMixin = new SuggestionControllerMixin(mContext, mLifecycle);
+ final LoaderManager loaderManager = mock(LoaderManager.class);
+ when(mHost.getLoaderManager()).thenReturn(loaderManager);
+
+ mMixin = new SuggestionControllerMixin(mContext, mHost, mLifecycle);
mMixin.onServiceConnected();
- assertThat(ShadowSuggestionController.sGetSuggestionCalled).isTrue();
+ verify(loaderManager).restartLoader(SuggestionLoader.LOADER_ID_SUGGESTIONS,
+ null /* args */, mMixin /* callback */);
}
-
}
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 c343f97..0650d5b 100644
--- a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImplTest.java
@@ -35,6 +35,7 @@
import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager;
import android.provider.Settings.Secure;
+import android.util.FeatureFlagUtils;
import android.util.Pair;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -54,10 +55,12 @@
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.SettingsShadowResources;
+import com.android.settings.testutils.shadow.SettingsShadowSystemProperties;
import com.android.settings.testutils.shadow.ShadowSecureSettings;
import com.android.settingslib.drawer.Tile;
import com.android.settingslib.suggestions.SuggestionParser;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -69,6 +72,7 @@
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
+import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@@ -76,7 +80,10 @@
@Config(
manifest = TestConfig.MANIFEST_PATH,
sdk = TestConfig.SDK_VERSION,
- shadows = {ShadowSecureSettings.class, SettingsShadowResources.class}
+ shadows = {ShadowSecureSettings.class,
+ SettingsShadowResources.class,
+ SettingsShadowSystemProperties.class
+ }
)
public class SuggestionFeatureProviderImplTest {
@@ -122,6 +129,11 @@
mProvider = new SuggestionFeatureProviderImpl(mContext);
}
+ @After
+ public void tearDown() {
+ SettingsShadowSystemProperties.clear();
+ }
+
@Test
public void isSuggestionCompleted_doubleTapPower_trueWhenNotAvailable() {
SettingsShadowResources.overrideResource(
@@ -289,6 +301,23 @@
}
@Test
+ public void isSuggestionV2Enabled_isNotLowMemoryDevice_sysPropOn_shouldReturnTrue() {
+ when(mActivityManager.isLowRamDevice()).thenReturn(false);
+ SettingsShadowSystemProperties.set(
+ FeatureFlagUtils.FFLAG_PREFIX + mProvider.FEATURE_FLAG_SUGGESTIONS_V2, "true");
+ assertThat(mProvider.isSuggestionV2Enabled(mContext)).isTrue();
+ }
+
+ @Test
+ public void isSuggestionV2Enabled_isNotLowMemoryDevice_sysPropOff_shouldReturnTrue() {
+ when(mActivityManager.isLowRamDevice()).thenReturn(false);
+ SettingsShadowSystemProperties.set(
+ FeatureFlagUtils.FFLAG_PREFIX + mProvider.FEATURE_FLAG_SUGGESTIONS_V2, "false");
+ assertThat(mProvider.isSuggestionV2Enabled(mContext)).isFalse();
+ }
+
+
+ @Test
public void dismissSuggestion_noParserOrSuggestion_noop() {
mProvider.dismissSuggestion(mContext, null, null);
mProvider.dismissSuggestion(mContext, mSuggestionParser, null);
@@ -402,14 +431,35 @@
}
@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 a001aaf..13f7374 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 @@
}
@Test
+ @Config(shadows = {
+ ShadowPictureColorModePreferenceController.class
+ })
public void searchIndex_pageEnabled_shouldNotAddKeysToNonIndexable() {
final Context appContext = RuntimeEnvironment.application;
DevelopmentSettingsEnabler.setDevelopmentSettingsEnabled(appContext, true);
@@ -161,6 +168,24 @@
.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 @@
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 0000000..1367870
--- /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 0000000..5cf4e10
--- /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 0f61a5d..8b2a27b 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 @@
int userHandle) {
final Table<Integer, String, Object> 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;
}
}