Merge "Don't plot usage data beyond current date."
diff --git a/res/drawable/ic_find_in_page_24px.xml b/res/drawable/ic_find_in_page_24px.xml
new file mode 100644
index 0000000..18895e4
--- /dev/null
+++ b/res/drawable/ic_find_in_page_24px.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M6,2C4.9,2 4.01,2.9 4.01,4L4,20c0,1.1 0.89,2 1.99,2H18c1.1,0 2,-0.9 2,-2V8l-6,-6H6zM18,17.59l-2.2,-2.2c0.44,-0.69 0.7,-1.51 0.7,-2.39c0,-2.48 -2.02,-4.5 -4.5,-4.5S7.5,10.52 7.5,13s2.02,4.5 4.5,4.5c0.88,0 1.69,-0.26 2.39,-0.7l3.2,3.2L6,20V4h7.17L18,8.83V17.59zM12,15.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5s2.5,1.12 2.5,2.5S13.38,15.5 12,15.5z"/>
+</vector>
diff --git a/res/menu/manage_apps.xml b/res/menu/manage_apps.xml
index 99dba37..51189a0 100644
--- a/res/menu/manage_apps.xml
+++ b/res/menu/manage_apps.xml
@@ -16,6 +16,13 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
+ android:id="@+id/search_app_list_menu"
+ android:title="@string/search_settings"
+ android:icon="@drawable/ic_find_in_page_24px"
+ android:showAsAction="always|collapseActionView"
+ android:actionViewClass="android.widget.SearchView" />
+
+ <item
android:id="@+id/advanced"
android:title="@string/advanced_apps"
android:icon="@drawable/ic_settings_24dp"
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 752fd3d..86763b7 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -72,6 +72,14 @@
<attr name="allowDynamicSummaryInSlice" format="boolean" />
</declare-styleable>
+ <declare-styleable name="PreferenceScreen">
+ <!-- Determines if static preferences defined in addStaticPreferences are added before or after the radio buttons -->
+ <attr name="staticPreferenceLocation">
+ <enum name="prepend" value="0" />
+ <enum name="append" value="1" />
+ </attr>
+ </declare-styleable>
+
<!-- For DotsPageIndicator -->
<declare-styleable name="DotsPageIndicator">
<attr name="dotDiameter" format="dimension" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 44d43c3..63a7662 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -5406,6 +5406,12 @@
<!-- Battery saver: Label for preference to turn on battery saver automatically when battery is low [CHAR_LIMIT=40] -->
<string name="battery_saver_auto_title">Turn on automatically</string>
+ <!-- Battery saver: Label for preference to indicate there is no battery saver schedule [CHAR_LIMIT=40] -->
+ <string name="battery_saver_auto_no_schedule">No schedule</string>
+
+ <!-- Battery saver: Title for battery saver schedule screen [CHAR_LIMIT=40] -->
+ <string name="battery_saver_schedule_settings_title">Set a schedule</string>
+
<!-- Battery saver: Label for seekbar to change battery saver threshold [CHAR_LIMIT=40] -->
<string name="battery_saver_seekbar_title">At <xliff:g id="percent">%1$s</xliff:g></string>
@@ -8970,7 +8976,7 @@
<string name="condition_cellular_title">Mobile data is off</string>
<!-- Summary of condition that cellular data is off [CHAR LIMIT=NONE] -->
- <string name="condition_cellular_summary">Internet is available only via Wi-Fi</string>
+ <string name="condition_cellular_summary">Internet only available via Wi\u2011Fi</string>
<!-- Title of condition that background data is off [CHAR LIMIT=30] -->
<string name="condition_bg_data_title">Data Saver</string>
@@ -8982,7 +8988,7 @@
<string name="condition_work_title">Work profile is off</string>
<!-- Summary of condition that work mode is off [CHAR LIMIT=NONE] -->
- <string name="condition_work_summary">Apps, background sync, and other features related to your work profile are turned off.</string>
+ <string name="condition_work_summary">For apps & notifications</string>
<!-- Action label on device muted card - clicking action will turn on ringtone sound [CHAR LIMIT=50] -->
<string name="condition_device_muted_action_turn_on_sound">Turn on sound</string>
diff --git a/res/xml/battery_saver_schedule_settings.xml b/res/xml/battery_saver_schedule_settings.xml
new file mode 100644
index 0000000..f91e4ca
--- /dev/null
+++ b/res/xml/battery_saver_schedule_settings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:title="@string/battery_saver_schedule_settings_title"
+ settings:staticPreferenceLocation="append">
+</PreferenceScreen >
diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java
index 6b2fb91..c2db019 100644
--- a/src/com/android/settings/applications/manageapplications/ManageApplications.java
+++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java
@@ -67,11 +67,14 @@
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.Filter;
import android.widget.FrameLayout;
+import android.widget.SearchView;
import android.widget.Spinner;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@@ -140,7 +143,7 @@
* intent.
*/
public class ManageApplications extends InstrumentedFragment
- implements View.OnClickListener, OnItemSelectedListener {
+ implements View.OnClickListener, OnItemSelectedListener, SearchView.OnQueryTextListener {
static final String TAG = "ManageApplications";
static final boolean DEBUG = true;
@@ -196,6 +199,7 @@
private View mListContainer;
private RecyclerView mRecyclerView;
+ private SearchView mSearchView;
// Size resource used for packages whose size computation failed for some reason
CharSequence mInvalidSizeStr;
@@ -599,6 +603,13 @@
mOptionsMenu = menu;
inflater.inflate(R.menu.manage_apps, menu);
+ final MenuItem searchMenuItem = menu.findItem(R.id.search_app_list_menu);
+ if (searchMenuItem != null) {
+ mSearchView = (SearchView) searchMenuItem.getActionView();
+ mSearchView.setQueryHint(getText(R.string.search_settings));
+ mSearchView.setOnQueryTextListener(this);
+ }
+
updateOptionsMenu();
}
@@ -724,6 +735,17 @@
public void onNothingSelected(AdapterView<?> parent) {
}
+ @Override
+ public boolean onQueryTextSubmit(String query) {
+ return false;
+ }
+
+ @Override
+ public boolean onQueryTextChange(String newText) {
+ mApplications.filterSearch(newText);
+ return false;
+ }
+
public void updateView() {
updateOptionsMenu();
final Activity host = getActivity();
@@ -859,6 +881,7 @@
private AppFilterItem mAppFilter;
private ArrayList<ApplicationsState.AppEntry> mEntries;
+ private ArrayList<ApplicationsState.AppEntry> mOriginalEntries;
private boolean mResumed;
private int mLastSortMode = -1;
private int mWhichSize = SIZE_TOTAL;
@@ -866,6 +889,7 @@
private boolean mHasReceivedLoadEntries;
private boolean mHasReceivedBridgeCallback;
private FileViewHolderController mExtraViewController;
+ private SearchFilter mSearchFilter;
// This is to remember and restore the last scroll position when this
// fragment is paused. We need this special handling because app entries are added gradually
@@ -1100,6 +1124,13 @@
});
}
+ public void filterSearch(String query) {
+ if (mSearchFilter == null) {
+ mSearchFilter = new SearchFilter();
+ }
+ mSearchFilter.filter(query);
+ }
+
@VisibleForTesting
static boolean shouldUseStableItemHeight(int listType) {
return true;
@@ -1146,6 +1177,7 @@
entries = removeDuplicateIgnoringUser(entries);
}
mEntries = entries;
+ mOriginalEntries = entries;
notifyDataSetChanged();
if (getItemCount() == 0) {
mManageApplications.mRecyclerView.setVisibility(View.GONE);
@@ -1153,6 +1185,14 @@
} else {
mManageApplications.mEmptyView.setVisibility(View.GONE);
mManageApplications.mRecyclerView.setVisibility(View.VISIBLE);
+
+ if (mManageApplications.mSearchView != null
+ && mManageApplications.mSearchView.isVisibleToUser()) {
+ final CharSequence query = mManageApplications.mSearchView.getQuery();
+ if (!TextUtils.isEmpty(query)) {
+ filterSearch(query.toString());
+ }
+ }
}
// Restore the last scroll position if the number of entries added so far is bigger than
// it.
@@ -1405,6 +1445,38 @@
}
}
}
+
+ /**
+ * An array filter that constrains the content of the array adapter with a substring.
+ * Item that does not contains the specified substring will be removed from the list.</p>
+ */
+ private class SearchFilter extends Filter {
+ @WorkerThread
+ @Override
+ protected FilterResults performFiltering(CharSequence query) {
+ final ArrayList<ApplicationsState.AppEntry> matchedEntries;
+ if (TextUtils.isEmpty(query)) {
+ matchedEntries = mOriginalEntries;
+ } else {
+ matchedEntries = new ArrayList<>();
+ for (ApplicationsState.AppEntry entry : mOriginalEntries) {
+ if (entry.label.toLowerCase().contains(query.toString().toLowerCase())) {
+ matchedEntries.add(entry);
+ }
+ }
+ }
+ final FilterResults results = new FilterResults();
+ results.values = matchedEntries;
+ results.count = matchedEntries.size();
+ return results;
+ }
+
+ @Override
+ protected void publishResults(CharSequence constraint, FilterResults results) {
+ mEntries = (ArrayList<ApplicationsState.AppEntry>) results.values;
+ notifyDataSetChanged();
+ }
+ }
}
private static class SummaryProvider implements SummaryLoader.SummaryProvider {
diff --git a/src/com/android/settings/core/PreferenceXmlParserUtils.java b/src/com/android/settings/core/PreferenceXmlParserUtils.java
index 9fdeeef..ce5c505 100644
--- a/src/com/android/settings/core/PreferenceXmlParserUtils.java
+++ b/src/com/android/settings/core/PreferenceXmlParserUtils.java
@@ -55,6 +55,8 @@
private static final List<String> SUPPORTED_PREF_TYPES = Arrays.asList(
"Preference", "PreferenceCategory", "PreferenceScreen",
"com.android.settings.widget.WorkOnlyCategory");
+ public static final int PREPEND_VALUE = 0;
+ public static final int APPEND_VALUE = 1;
/**
* Flag definition to indicate which metadata should be extracted when
@@ -84,6 +86,7 @@
int FLAG_NEED_KEYWORDS = 1 << 8;
int FLAG_NEED_SEARCHABLE = 1 << 9;
int FLAG_ALLOW_DYNAMIC_SUMMARY_IN_SLICE = 1 << 10;
+ int FLAG_NEED_PREF_APPEND = 1 << 11;
}
public static final String METADATA_PREF_TYPE = "type";
@@ -97,6 +100,7 @@
public static final String METADATA_SEARCHABLE = "searchable";
public static final String METADATA_ALLOW_DYNAMIC_SUMMARY_IN_SLICE =
"allow_dynamic_summary_in_slice";
+ public static final String METADATA_APPEND = "staticPreferenceLocation";
private static final String ENTRIES_SEPARATOR = "|";
@@ -184,14 +188,13 @@
// Parse next until start tag is found
}
final int outerDepth = parser.getDepth();
-
+ final boolean hasPrefScreenFlag = hasFlag(flags, MetadataFlag.FLAG_INCLUDE_PREF_SCREEN);
do {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String nodeName = parser.getName();
- if (!hasFlag(flags, MetadataFlag.FLAG_INCLUDE_PREF_SCREEN)
- && TextUtils.equals(PREF_SCREEN_TAG, nodeName)) {
+ if (!hasPrefScreenFlag && TextUtils.equals(PREF_SCREEN_TAG, nodeName)) {
continue;
}
if (!SUPPORTED_PREF_TYPES.contains(nodeName) && !nodeName.endsWith("Preference")) {
@@ -199,8 +202,14 @@
}
final Bundle preferenceMetadata = new Bundle();
final AttributeSet attrs = Xml.asAttributeSet(parser);
+
final TypedArray preferenceAttributes = context.obtainStyledAttributes(attrs,
R.styleable.Preference);
+ TypedArray preferenceScreenAttributes = null;
+ if (hasPrefScreenFlag) {
+ preferenceScreenAttributes = context.obtainStyledAttributes(
+ attrs, R.styleable.PreferenceScreen);
+ }
if (hasFlag(flags, MetadataFlag.FLAG_NEED_PREF_TYPE)) {
preferenceMetadata.putString(METADATA_PREF_TYPE, nodeName);
@@ -236,6 +245,10 @@
preferenceMetadata.putBoolean(METADATA_ALLOW_DYNAMIC_SUMMARY_IN_SLICE,
isDynamicSummaryAllowed(preferenceAttributes));
}
+ if (hasFlag(flags, MetadataFlag.FLAG_NEED_PREF_APPEND) && hasPrefScreenFlag) {
+ preferenceMetadata.putBoolean(METADATA_APPEND,
+ isAppended(preferenceScreenAttributes));
+ }
metadata.add(preferenceMetadata);
preferenceAttributes.recycle();
@@ -325,7 +338,12 @@
false /* default */);
}
- private static String getKeywords(TypedArray styleAttributes) {
- return styleAttributes.getString(R.styleable.Preference_keywords);
+ private static String getKeywords(TypedArray styledAttributes) {
+ return styledAttributes.getString(R.styleable.Preference_keywords);
+ }
+
+ private static boolean isAppended(TypedArray styledAttributes) {
+ return styledAttributes.getInt(R.styleable.PreferenceScreen_staticPreferenceLocation,
+ PREPEND_VALUE) == APPEND_VALUE;
}
}
diff --git a/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceController.java b/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceController.java
index 0fcec05..95e663b 100644
--- a/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceController.java
+++ b/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceController.java
@@ -66,6 +66,25 @@
}
}
+ @Override
+ protected void onDeveloperOptionsSwitchDisabled() {
+ super.onDeveloperOptionsSwitchDisabled();
+ final boolean offloadSupported =
+ SystemProperties.getBoolean(A2DP_OFFLOAD_SUPPORTED_PROPERTY, false);
+ if (offloadSupported) {
+ ((SwitchPreference) mPreference).setChecked(false);
+ SystemProperties.set(A2DP_OFFLOAD_DISABLED_PROPERTY, "false");
+ }
+ }
+
+ public boolean isDefaultValue() {
+ final boolean offloadSupported =
+ SystemProperties.getBoolean(A2DP_OFFLOAD_SUPPORTED_PROPERTY, false);
+ final boolean offloadDisabled =
+ SystemProperties.getBoolean(A2DP_OFFLOAD_DISABLED_PROPERTY, false);
+ return offloadSupported ? !offloadDisabled : true;
+ }
+
public void onA2dpHwDialogConfirmed() {
final boolean offloadDisabled =
SystemProperties.getBoolean(A2DP_OFFLOAD_DISABLED_PROPERTY, false);
diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
index 762686a..4ddcc36 100644
--- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
+++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
@@ -221,7 +221,16 @@
if (isChecked) {
EnableDevelopmentSettingWarningDialog.show(this /* host */);
} else {
- disableDeveloperOptions();
+ final BluetoothA2dpHwOffloadPreferenceController controller =
+ getDevelopmentOptionsController(
+ BluetoothA2dpHwOffloadPreferenceController.class);
+ // If A2DP hardware offload isn't default value, we must reboot after disable
+ // developer options. Show a dialog for the user to confirm.
+ if (controller == null || controller.isDefaultValue()) {
+ disableDeveloperOptions();
+ } else {
+ DisableDevSettingsDialogFragment.show(this /* host */);
+ }
}
}
}
@@ -380,6 +389,15 @@
mSwitchBar.setChecked(false);
}
+ void onDisableDevelopmentOptionsConfirmed() {
+ disableDeveloperOptions();
+ }
+
+ void onDisableDevelopmentOptionsRejected() {
+ // Reset the toggle
+ mSwitchBar.setChecked(true);
+ }
+
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
Activity activity, Lifecycle lifecycle, DevelopmentSettingsDashboardFragment fragment,
BluetoothA2dpConfigStore bluetoothA2dpConfigStore) {
diff --git a/src/com/android/settings/development/DisableDevSettingsDialogFragment.java b/src/com/android/settings/development/DisableDevSettingsDialogFragment.java
new file mode 100644
index 0000000..9b3ba58
--- /dev/null
+++ b/src/com/android/settings/development/DisableDevSettingsDialogFragment.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.development;
+
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.util.Log;
+import androidx.annotation.VisibleForTesting;
+import androidx.fragment.app.Fragment;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.FragmentManager;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+
+public class DisableDevSettingsDialogFragment extends InstrumentedDialogFragment
+ implements DialogInterface.OnClickListener {
+
+ public static final String TAG = "DisableDevSettingDlg";
+
+ @VisibleForTesting
+ static DisableDevSettingsDialogFragment newInstance() {
+ final DisableDevSettingsDialogFragment dialog = new DisableDevSettingsDialogFragment();
+ return dialog;
+ }
+
+ public static void show(DevelopmentSettingsDashboardFragment host) {
+ final DisableDevSettingsDialogFragment dialog = new DisableDevSettingsDialogFragment();
+ dialog.setTargetFragment(host, 0 /* requestCode */);
+ final FragmentManager manager = host.getActivity().getSupportFragmentManager();
+ dialog.show(manager, TAG);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsProto.MetricsEvent.DIALOG_DISABLE_DEVELOPMENT_OPTIONS;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ // Reuse the same text of disable_a2dp_hw_offload_dialog.
+ // The text is generic enough to be used for turning off Dev options.
+ return new AlertDialog.Builder(getActivity())
+ .setMessage(R.string.bluetooth_disable_a2dp_hw_offload_dialog_message)
+ .setTitle(R.string.bluetooth_disable_a2dp_hw_offload_dialog_title)
+ .setPositiveButton(
+ R.string.bluetooth_disable_a2dp_hw_offload_dialog_confirm, this)
+ .setNegativeButton(
+ R.string.bluetooth_disable_a2dp_hw_offload_dialog_cancel, this)
+ .create();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Fragment fragment = getTargetFragment();
+ if (!(fragment instanceof DevelopmentSettingsDashboardFragment)){
+ Log.e(TAG, "getTargetFragment return unexpected type");
+ }
+
+ final DevelopmentSettingsDashboardFragment host =
+ (DevelopmentSettingsDashboardFragment) fragment;
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ host.onDisableDevelopmentOptionsConfirmed();
+ PowerManager pm = getContext().getSystemService(PowerManager.class);
+ pm.reboot(null);
+ } else {
+ host.onDisableDevelopmentOptionsRejected();
+ }
+ }
+}
diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java b/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java
index e437e2b..32a86e8 100644
--- a/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java
+++ b/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java
@@ -16,8 +16,10 @@
package com.android.settings.homepage.contextualcards;
+import android.annotation.NonNull;
import android.content.Context;
import android.content.Intent;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
@@ -25,6 +27,7 @@
import androidx.slice.widget.EventInfo;
import com.android.settings.R;
+import com.android.settings.intelligence.ContextualCardProto.ContextualCardList;
import java.util.List;
@@ -38,10 +41,6 @@
// Contextual card shows, log card name and rank
private static final int CONTEXTUAL_CARD_SHOW = 39;
- // Contextual card is eligible to be shown, but doesn't rank high
- // enough, log card name and score
- private static final int CONTEXTUAL_CARD_NOT_SHOW = 40;
-
// Contextual card is dismissed, log card name
private static final int CONTEXTUAL_CARD_DISMISS = 41;
@@ -67,6 +66,11 @@
// log type
private static final String EXTRA_CONTEXTUALCARD_ACTION_TYPE = "type";
+ // displayed contextual cards
+ private static final String EXTRA_CONTEXTUALCARD_VISIBLE = "visible";
+
+ // hidden contextual cards
+ private static final String EXTRA_CONTEXTUALCARD_HIDDEN = "hidden";
// Contextual card tap target
private static final int TARGET_DEFAULT = 0;
@@ -82,6 +86,10 @@
@Override
public void logHomepageDisplay(Context context, Long latency) {
+ final Intent intent = new Intent();
+ intent.putExtra(EXTRA_CONTEXTUALCARD_ACTION_TYPE, CONTEXTUAL_HOME_SHOW);
+ intent.putExtra(EXTRA_LATENCY, latency);
+ sendBroadcast(context, intent);
}
@Override
@@ -94,8 +102,13 @@
}
@Override
- public void logContextualCardDisplay(Context context, List<ContextualCard> showCards,
+ public void logContextualCardDisplay(Context context, List<ContextualCard> visibleCards,
List<ContextualCard> hiddenCards) {
+ final Intent intent = new Intent();
+ intent.putExtra(EXTRA_CONTEXTUALCARD_ACTION_TYPE, CONTEXTUAL_CARD_SHOW);
+ intent.putExtra(EXTRA_CONTEXTUALCARD_VISIBLE, serialize(visibleCards));
+ intent.putExtra(EXTRA_CONTEXTUALCARD_HIDDEN, serialize(hiddenCards));
+ sendBroadcast(context, intent);
}
@Override
@@ -116,7 +129,7 @@
final String action = context.getString(R.string.config_settingsintelligence_log_action);
if (!TextUtils.isEmpty(action)) {
intent.setAction(action);
- context.sendBroadcast(intent);
+ context.sendBroadcastAsUser(intent, UserHandle.ALL);
}
}
@@ -133,4 +146,16 @@
return TARGET_DEFAULT;
}
}
+
+ @VisibleForTesting
+ @NonNull
+ byte[] serialize(List<ContextualCard> cards) {
+ final ContextualCardList.Builder builder = ContextualCardList.newBuilder();
+ cards.stream().forEach(card -> builder.addCard(
+ com.android.settings.intelligence.ContextualCardProto.ContextualCard.newBuilder()
+ .setSliceUri(card.getSliceUri().toString())
+ .setCardName(card.getName())
+ .build()));
+ return builder.build().toByteArray();
+ }
}
diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java b/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java
index 3ef4653..6373519 100644
--- a/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java
+++ b/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java
@@ -20,6 +20,9 @@
import static androidx.slice.widget.SliceLiveData.SUPPORTED_SPECS;
+import static com.android.settings.slices.CustomSliceRegistry.CONNECTED_DEVICE_SLICE_URI;
+import static com.android.settings.slices.CustomSliceRegistry.WIFI_SLICE_URI;
+
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
@@ -34,7 +37,7 @@
import androidx.annotation.VisibleForTesting;
import androidx.slice.Slice;
-import com.android.settings.slices.CustomSliceRegistry;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.utils.AsyncLoaderCompat;
import java.util.ArrayList;
@@ -103,27 +106,50 @@
return getFinalDisplayableCards(result);
}
+ // Get final displayed cards and log what cards will be displayed/hidden
@VisibleForTesting
List<ContextualCard> getFinalDisplayableCards(List<ContextualCard> candidates) {
- List<ContextualCard> eligibleCards = filterEligibleCards(candidates);
- eligibleCards = eligibleCards.stream().limit(DEFAULT_CARD_COUNT).collect(
- Collectors.toList());
+ final List<ContextualCard> eligibleCards = filterEligibleCards(candidates);
+ final List<ContextualCard> visibleCards = new ArrayList<>();
+ final List<ContextualCard> hiddenCards = new ArrayList<>();
- if (eligibleCards.size() <= 2 || getNumberOfLargeCard(eligibleCards) == 0) {
- return eligibleCards;
+ final int size = eligibleCards.size();
+ for (int i = 0; i < size; i++) {
+ if (i < DEFAULT_CARD_COUNT) {
+ visibleCards.add(eligibleCards.get(i));
+ } else {
+ hiddenCards.add(eligibleCards.get(i));
+ }
}
- if (eligibleCards.size() == DEFAULT_CARD_COUNT) {
- eligibleCards.remove(eligibleCards.size() - 1);
+ try {
+ // The maximum cards are four small cards OR
+ // one large card with two small cards OR
+ // two large cards
+ if (visibleCards.size() <= 2 || getNumberOfLargeCard(visibleCards) == 0) {
+ // four small cards
+ return visibleCards;
+ }
+
+ if (visibleCards.size() == DEFAULT_CARD_COUNT) {
+ hiddenCards.add(visibleCards.remove(visibleCards.size() - 1));
+ }
+
+ if (getNumberOfLargeCard(visibleCards) == 1) {
+ // One large card with two small cards
+ return visibleCards;
+ }
+
+ hiddenCards.add(visibleCards.remove(visibleCards.size() - 1));
+
+ // Two large cards
+ return visibleCards;
+ } finally {
+ final ContextualCardFeatureProvider contextualCardFeatureProvider =
+ FeatureFactory.getFactory(mContext).getContextualCardFeatureProvider();
+ contextualCardFeatureProvider.logContextualCardDisplay(mContext, visibleCards,
+ hiddenCards);
}
-
- if (getNumberOfLargeCard(eligibleCards) == 1) {
- return eligibleCards;
- }
-
- eligibleCards.remove(eligibleCards.size() - 1);
-
- return eligibleCards;
}
@VisibleForTesting
@@ -169,8 +195,8 @@
private int getNumberOfLargeCard(List<ContextualCard> cards) {
return (int) cards.stream()
- .filter(card -> card.getSliceUri().equals(CustomSliceRegistry.WIFI_SLICE_URI)
- || card.getSliceUri().equals(CustomSliceRegistry.CONNECTED_DEVICE_SLICE_URI))
+ .filter(card -> card.getSliceUri().equals(WIFI_SLICE_URI)
+ || card.getSliceUri().equals(CONNECTED_DEVICE_SLICE_URI))
.count();
}
diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java b/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java
index 5f397d7..28ad0d3 100644
--- a/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java
+++ b/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java
@@ -16,8 +16,7 @@
package com.android.settings.homepage.contextualcards;
-import static com.android.settings.homepage.contextualcards.ContextualCardLoader
- .CARD_CONTENT_LOADER_ID;
+import static com.android.settings.homepage.contextualcards.ContextualCardLoader.CARD_CONTENT_LOADER_ID;
import static java.util.stream.Collectors.groupingBy;
@@ -33,6 +32,7 @@
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
@@ -72,6 +72,7 @@
private final List<LifecycleObserver> mLifecycleObservers;
private ContextualCardUpdateListener mListener;
+ private long mStartTime;
public ContextualCardManager(Context context, Lifecycle lifecycle) {
mContext = context;
@@ -86,6 +87,7 @@
}
void loadContextualCards(ContextualCardsFragment fragment) {
+ mStartTime = System.currentTimeMillis();
final CardContentLoaderCallbacks cardContentLoaderCallbacks =
new CardContentLoaderCallbacks(mContext);
cardContentLoaderCallbacks.setListener(this);
@@ -168,6 +170,10 @@
@Override
public void onFinishCardLoading(List<ContextualCard> cards) {
onContextualCardUpdated(cards.stream().collect(groupingBy(ContextualCard::getCardType)));
+ final long elapsedTime = System.currentTimeMillis() - mStartTime;
+ final ContextualCardFeatureProvider contextualCardFeatureProvider =
+ FeatureFactory.getFactory(mContext).getContextualCardFeatureProvider();
+ contextualCardFeatureProvider.logHomepageDisplay(mContext, elapsedTime);
}
public ControllerRendererPool getControllerRendererPool() {
diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java
index 267fe4d..be03c28 100644
--- a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java
+++ b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java
@@ -41,6 +41,7 @@
import androidx.slice.widget.SliceView;
import com.android.settings.R;
+import com.android.settings.homepage.contextualcards.CardContentProvider;
import com.android.settings.homepage.contextualcards.ContextualCard;
import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider;
import com.android.settings.homepage.contextualcards.ContextualCardRenderer;
@@ -94,6 +95,8 @@
public void bindView(RecyclerView.ViewHolder holder, ContextualCard card) {
final SliceViewHolder cardHolder = (SliceViewHolder) holder;
final Uri uri = card.getSliceUri();
+ //TODO(b/120629936): Take this out once blank card issue is fixed.
+ Log.d(TAG, "bindView - uri = " + uri);
if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
Log.w(TAG, "Invalid uri, skipping slice: " + uri);
@@ -116,6 +119,11 @@
sliceLiveData.observe(mLifecycleOwner, slice -> {
if (slice == null) {
Log.w(TAG, "Slice is null");
+ mContext.getContentResolver().notifyChange(CardContentProvider.URI, null);
+ return;
+ } else {
+ //TODO(b/120629936): Take this out once blank card issue is fixed.
+ Log.d(TAG, "Slice callback - uri = " + slice.getUri());
}
cardHolder.sliceView.setSlice(slice);
});
diff --git a/src/com/android/settings/slices/SliceBuilderUtils.java b/src/com/android/settings/slices/SliceBuilderUtils.java
index c16a8ba..d75eaa2 100644
--- a/src/com/android/settings/slices/SliceBuilderUtils.java
+++ b/src/com/android/settings/slices/SliceBuilderUtils.java
@@ -449,6 +449,12 @@
if (iconResource == 0) {
iconResource = R.drawable.ic_settings;
}
- return IconCompat.createWithResource(context, iconResource);
+ try {
+ return IconCompat.createWithResource(context, iconResource);
+ } catch (Exception e) {
+ Log.w(TAG, "Falling back to settings icon because there is an error getting slice icon "
+ + data.getUri(), e);
+ return IconCompat.createWithResource(context, R.drawable.ic_settings);
+ }
}
}
diff --git a/src/com/android/settings/widget/RadioButtonPickerFragment.java b/src/com/android/settings/widget/RadioButtonPickerFragment.java
index 89df487..50c1b58 100644
--- a/src/com/android/settings/widget/RadioButtonPickerFragment.java
+++ b/src/com/android/settings/widget/RadioButtonPickerFragment.java
@@ -22,10 +22,12 @@
import android.os.UserManager;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.Toast;
import androidx.annotation.LayoutRes;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
@@ -34,16 +36,23 @@
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.core.InstrumentedPreferenceFragment;
+import com.android.settings.core.PreferenceXmlParserUtils;
+import com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag;
import com.android.settingslib.widget.CandidateInfo;
+import java.io.IOException;
import java.util.List;
import java.util.Map;
+import org.xmlpull.v1.XmlPullParserException;
public abstract class RadioButtonPickerFragment extends InstrumentedPreferenceFragment implements
RadioButtonPreference.OnClickListener {
- @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ @VisibleForTesting
static final String EXTRA_FOR_WORK = "for_work";
+ private static final String TAG = "RadioButtonPckrFrgmt";
+ @VisibleForTesting
+ boolean mAppendStaticPreferences = false;
private final Map<String, CandidateInfo> mCandidates = new ArrayMap<>();
@@ -69,6 +78,19 @@
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
super.onCreatePreferences(savedInstanceState, rootKey);
+ try {
+ // Check if the xml specifies if static preferences should go on the top or bottom
+ final List<Bundle> metadata = PreferenceXmlParserUtils.extractMetadata(getContext(),
+ getPreferenceScreenResId(),
+ MetadataFlag.FLAG_INCLUDE_PREF_SCREEN |
+ MetadataFlag.FLAG_NEED_PREF_APPEND);
+ mAppendStaticPreferences = metadata.get(0)
+ .getBoolean(PreferenceXmlParserUtils.METADATA_APPEND);
+ } catch (IOException e) {
+ Log.e(TAG, "Error trying to open xml file", e);
+ } catch (XmlPullParserException e) {
+ Log.e(TAG, "Error parsing xml", e);
+ }
updateCandidates();
}
@@ -142,7 +164,9 @@
final String systemDefaultKey = getSystemDefaultKey();
final PreferenceScreen screen = getPreferenceScreen();
screen.removeAll();
- addStaticPreferences(screen);
+ if (!mAppendStaticPreferences) {
+ addStaticPreferences(screen);
+ }
final int customLayoutResId = getRadioButtonPreferenceCustomLayoutResId();
if (shouldShowItemNone()) {
@@ -168,6 +192,9 @@
}
}
mayCheckOnlyRadioButton();
+ if (mAppendStaticPreferences) {
+ addStaticPreferences(screen);
+ }
}
@VisibleForTesting
diff --git a/tests/robotests/res/xml-mcc999/battery_saver_schedule_settings.xml b/tests/robotests/res/xml-mcc999/battery_saver_schedule_settings.xml
new file mode 100644
index 0000000..f91e4ca
--- /dev/null
+++ b/tests/robotests/res/xml-mcc999/battery_saver_schedule_settings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:title="@string/battery_saver_schedule_settings_title"
+ settings:staticPreferenceLocation="append">
+</PreferenceScreen >
diff --git a/tests/robotests/src/com/android/settings/applications/manageapplications/ManageApplicationsTest.java b/tests/robotests/src/com/android/settings/applications/manageapplications/ManageApplicationsTest.java
index b3f5b5e..4c9cacb 100644
--- a/tests/robotests/src/com/android/settings/applications/manageapplications/ManageApplicationsTest.java
+++ b/tests/robotests/src/com/android/settings/applications/manageapplications/ManageApplicationsTest.java
@@ -28,11 +28,12 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -48,9 +49,11 @@
import android.os.UserManager;
import android.view.LayoutInflater;
import android.view.Menu;
+import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.SearchView;
import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.RecyclerView;
@@ -159,6 +162,35 @@
}
@Test
+ public void onCreateOptionsMenu_shouldSetSearchQueryListener() {
+ final SearchView searchView = mock(SearchView.class);
+ final MenuItem searchMenu = mock(MenuItem.class);
+ final MenuItem helpMenu = mock(MenuItem.class);
+ when(searchMenu.getActionView()).thenReturn(searchView);
+ when(mMenu.findItem(R.id.search_app_list_menu)).thenReturn(searchMenu);
+ when(mMenu.add(anyInt() /* groupId */, anyInt() /* itemId */, anyInt() /* order */,
+ anyInt() /* titleRes */)).thenReturn(helpMenu);
+ doReturn("Test").when(mFragment).getText(anyInt() /* resId */);
+ doNothing().when(mFragment).updateOptionsMenu();
+
+ mFragment.onCreateOptionsMenu(mMenu, mock(MenuInflater.class));
+
+ verify(searchView).setOnQueryTextListener(mFragment);
+ }
+
+ @Test
+ public void onQueryTextChange_shouldFilterSearchInApplicationsAdapter() {
+ final ManageApplications.ApplicationsAdapter adapter =
+ mock(ManageApplications.ApplicationsAdapter.class);
+ final String query = "Test App";
+ ReflectionHelpers.setField(mFragment, "mApplications", adapter);
+
+ mFragment.onQueryTextChange(query);
+
+ verify(adapter).filterSearch(query);
+ }
+
+ @Test
public void updateLoading_appLoaded_shouldNotDelayCallToHandleLoadingContainer() {
ReflectionHelpers.setField(mFragment, "mLoadingContainer", mock(View.class));
ReflectionHelpers.setField(mFragment, "mListContainer", mock(View.class));
@@ -250,6 +282,34 @@
}
@Test
+ public void onRebuildComplete_hasSearchQuery_shouldFilterSearch() {
+ final String query = "Test";
+ final RecyclerView recyclerView = mock(RecyclerView.class);
+ final View emptyView = mock(View.class);
+ ReflectionHelpers.setField(mFragment, "mRecyclerView", recyclerView);
+ ReflectionHelpers.setField(mFragment, "mEmptyView", emptyView);
+ final SearchView searchView = mock(SearchView.class);
+ ReflectionHelpers.setField(mFragment, "mSearchView", searchView);
+ when(searchView.isVisibleToUser()).thenReturn(true);
+ when(searchView.getQuery()).thenReturn(query);
+ final View listContainer = mock(View.class);
+ when(listContainer.getVisibility()).thenReturn(View.VISIBLE);
+ ReflectionHelpers.setField(mFragment, "mListContainer", listContainer);
+ ReflectionHelpers.setField(
+ mFragment, "mFilterAdapter", mock(ManageApplications.FilterSpinnerAdapter.class));
+ final ArrayList<ApplicationsState.AppEntry> appList = new ArrayList<>();
+ appList.add(mock(ApplicationsState.AppEntry.class));
+ final ManageApplications.ApplicationsAdapter adapter =
+ spy(new ManageApplications.ApplicationsAdapter(mState, mFragment,
+ AppFilterRegistry.getInstance().get(FILTER_APPS_ALL),
+ null /* savedInstanceState */));
+
+ adapter.onRebuildComplete(appList);
+
+ verify(adapter).filterSearch(query);
+ }
+
+ @Test
public void notifyItemChange_recyclerViewIdle_shouldNotify() {
final RecyclerView recyclerView = mock(RecyclerView.class);
final ManageApplications.ApplicationsAdapter adapter =
@@ -344,6 +404,48 @@
}
@Test
+ public void applicationsAdapter_filterSearch_emptyQuery_shouldShowFullList() {
+ final ManageApplications.ApplicationsAdapter adapter =
+ new ManageApplications.ApplicationsAdapter(
+ mState, mFragment, mock(AppFilterItem.class), Bundle.EMPTY);
+ final String[] appNames = {"Apricot", "Banana", "Cantaloupe", "Fig", "Mango"};
+ ReflectionHelpers.setField(adapter, "mOriginalEntries", getTestAppList(appNames));
+
+ adapter.filterSearch("");
+
+ assertThat(adapter.getItemCount()).isEqualTo(5);
+ }
+
+ @Test
+ public void applicationsAdapter_filterSearch_noMatch_shouldShowEmptyList() {
+ final ManageApplications.ApplicationsAdapter adapter =
+ new ManageApplications.ApplicationsAdapter(
+ mState, mFragment, mock(AppFilterItem.class), Bundle.EMPTY);
+ final String[] appNames = {"Apricot", "Banana", "Cantaloupe", "Fig", "Mango"};
+ ReflectionHelpers.setField(adapter, "mOriginalEntries", getTestAppList(appNames));
+
+ adapter.filterSearch("orange");
+
+ assertThat(adapter.getItemCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void applicationsAdapter_filterSearch_shouldShowMatchedItemsOnly() {
+ final ManageApplications.ApplicationsAdapter adapter =
+ new ManageApplications.ApplicationsAdapter(
+ mState, mFragment, mock(AppFilterItem.class), Bundle.EMPTY);
+ final String[] appNames = {"Apricot", "Banana", "Cantaloupe", "Fig", "Mango"};
+ ReflectionHelpers.setField(adapter, "mOriginalEntries", getTestAppList(appNames));
+
+ adapter.filterSearch("an");
+
+ assertThat(adapter.getItemCount()).isEqualTo(3);
+ assertThat(adapter.getAppEntry(0).label).isEqualTo("Banana");
+ assertThat(adapter.getAppEntry(1).label).isEqualTo("Cantaloupe");
+ assertThat(adapter.getAppEntry(2).label).isEqualTo("Mango");
+ }
+
+ @Test
public void sortOrderSavedOnRebuild() {
when(mUserManager.getProfileIdsWithDisabled(anyInt())).thenReturn(new int[]{});
ReflectionHelpers.setField(mFragment, "mUserManager", mUserManager);
@@ -375,4 +477,14 @@
return new RoboMenuItem(id);
});
}
+
+ private ArrayList<ApplicationsState.AppEntry> getTestAppList(String[] appNames) {
+ final ArrayList<ApplicationsState.AppEntry> appList = new ArrayList<>();
+ for (String name : appNames) {
+ final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
+ appEntry.label = name;
+ appList.add(appEntry);
+ }
+ return appList;
+ }
}
diff --git a/tests/robotests/src/com/android/settings/core/PreferenceXmlParserUtilsTest.java b/tests/robotests/src/com/android/settings/core/PreferenceXmlParserUtilsTest.java
index 3b1b5af..ebf3252 100644
--- a/tests/robotests/src/com/android/settings/core/PreferenceXmlParserUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/core/PreferenceXmlParserUtilsTest.java
@@ -18,6 +18,7 @@
import static com.android.settings.core.PreferenceXmlParserUtils
.METADATA_ALLOW_DYNAMIC_SUMMARY_IN_SLICE;
+import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_APPEND;
import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_KEY;
import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_KEYWORDS;
import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_SEARCHABLE;
@@ -315,6 +316,32 @@
}
}
+ @Test
+ @Config(qualifiers = "mcc999")
+ public void extractMetadata_requestAppendProperty_shouldDefaultToFalse()
+ throws Exception {
+ final List<Bundle> metadata = PreferenceXmlParserUtils.extractMetadata(mContext,
+ R.xml.display_settings,
+ MetadataFlag.FLAG_INCLUDE_PREF_SCREEN | MetadataFlag.FLAG_NEED_PREF_APPEND);
+
+ for (Bundle bundle : metadata) {
+ assertThat(bundle.getBoolean(METADATA_APPEND)).isFalse();
+ }
+ }
+
+ @Test
+ @Config(qualifiers = "mcc999")
+ public void extractMetadata_requestAppendProperty_shouldReturnCorrectValue()
+ throws Exception {
+ final List<Bundle> metadata = PreferenceXmlParserUtils.extractMetadata(mContext,
+ R.xml.battery_saver_schedule_settings,
+ MetadataFlag.FLAG_INCLUDE_PREF_SCREEN | MetadataFlag.FLAG_NEED_PREF_APPEND);
+
+ for (Bundle bundle : metadata) {
+ assertThat(bundle.getBoolean(METADATA_APPEND)).isTrue();
+ }
+ }
+
/**
* @param resId the ID for the XML preference
* @return an XML resource parser that points to the start tag
diff --git a/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java
index d2c9b65..5eb21bd 100644
--- a/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java
@@ -28,10 +28,15 @@
import android.provider.SearchIndexableResource;
import android.provider.Settings;
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.FragmentActivity;
+
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.SettingsShadowResources;
+import com.android.settings.testutils.shadow.SettingsShadowResourcesImpl;
+import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
import com.android.settings.testutils.shadow.ShadowUserManager;
import com.android.settings.widget.SwitchBar;
import com.android.settings.widget.ToggleSwitch;
@@ -47,12 +52,14 @@
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.androidx.fragment.FragmentController;
import org.robolectric.util.ReflectionHelpers;
import java.util.List;
@RunWith(SettingsRobolectricTestRunner.class)
-@Config(shadows = ShadowUserManager.class)
+@Config(shadows = {ShadowUserManager.class, ShadowAlertDialogCompat.class,
+ SettingsShadowResourcesImpl.class})
public class DevelopmentSettingsDashboardFragmentTest {
private ToggleSwitch mSwitch;
@@ -179,6 +186,29 @@
}
@Test
+ @Config(shadows = ShadowDisableDevSettingsDialogFragment.class)
+ public void onSwitchChanged_turnOff_andOffloadIsNotDefaultValue_shouldShowWarningDialog() {
+ final BluetoothA2dpHwOffloadPreferenceController controller =
+ mock(BluetoothA2dpHwOffloadPreferenceController.class);
+ when(mDashboard.getContext()).thenReturn(mContext);
+ when(mDashboard.getDevelopmentOptionsController(
+ BluetoothA2dpHwOffloadPreferenceController.class)).thenReturn(controller);
+ when(controller.isDefaultValue()).thenReturn(false);
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1);
+
+ mDashboard.onSwitchChanged(mSwitch, false /* isChecked */);
+
+ AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+ assertThat(dialog).isNotNull();
+ ShadowAlertDialogCompat shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog);
+ assertThat(shadowDialog.getTitle()).isEqualTo(
+ mContext.getString(R.string.bluetooth_disable_a2dp_hw_offload_dialog_title));
+ assertThat(shadowDialog.getMessage()).isEqualTo(
+ mContext.getString(R.string.bluetooth_disable_a2dp_hw_offload_dialog_message));
+ }
+
+ @Test
public void onOemUnlockDialogConfirmed_shouldCallControllerOemConfirmed() {
final OemUnlockPreferenceController controller = mock(OemUnlockPreferenceController.class);
doReturn(controller).when(mDashboard)
@@ -264,6 +294,18 @@
}
}
+ @Implements(DisableDevSettingsDialogFragment.class)
+ public static class ShadowDisableDevSettingsDialogFragment {
+
+ @Implementation
+ public static void show(DevelopmentSettingsDashboardFragment host) {
+ DisableDevSettingsDialogFragment mFragment =
+ spy(DisableDevSettingsDialogFragment.newInstance());
+ FragmentController.setupFragment(mFragment, FragmentActivity.class,
+ 0 /* containerViewId */, null /* bundle */);
+ }
+ }
+
@Implements(PictureColorModePreferenceController.class)
public static class ShadowPictureColorModePreferenceController {
@Implementation
diff --git a/tests/robotests/src/com/android/settings/display/ColorModePreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/display/ColorModePreferenceFragmentTest.java
index 859912a..57f0ebb 100644
--- a/tests/robotests/src/com/android/settings/display/ColorModePreferenceFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/display/ColorModePreferenceFragmentTest.java
@@ -209,6 +209,7 @@
@Test
public void onCreatePreferences_useNewTitle_shouldAddColorModePreferences() {
+ when(mFragment.getContext()).thenReturn(RuntimeEnvironment.application);
doNothing().when(mFragment).addPreferencesFromResource(anyInt());
doNothing().when(mFragment).updateCandidates();
diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImplTest.java
index 08631f7..b3f9411 100644
--- a/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImplTest.java
@@ -16,13 +16,20 @@
package com.android.settings.homepage.contextualcards;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Matchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.content.Intent;
+import android.net.Uri;
+import android.os.UserHandle;
+import com.android.settings.intelligence.ContextualCardProto.ContextualCardList;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import org.junit.Before;
@@ -31,6 +38,9 @@
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
+import java.util.ArrayList;
+import java.util.List;
+
@RunWith(SettingsRobolectricTestRunner.class)
public class ContextualCardFeatureProviderImplTest {
@@ -48,7 +58,7 @@
final Intent intent = new Intent();
mImpl.sendBroadcast(mContext, intent);
- verify(mContext, never()).sendBroadcast(intent);
+ verify(mContext, never()).sendBroadcastAsUser(intent, UserHandle.ALL);
}
@Test
@@ -57,6 +67,37 @@
final Intent intent = new Intent();
mImpl.sendBroadcast(mContext, intent);
- verify(mContext).sendBroadcast(intent);
+ verify(mContext).sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ @Test
+ @Config(qualifiers = "mcc999")
+ public void logContextualCardDisplay_hasAction_sendBroadcast() {
+ mImpl.logContextualCardDisplay(mContext, new ArrayList<>(), new ArrayList<>());
+
+ verify(mContext).sendBroadcastAsUser(any(Intent.class), any());
+ }
+
+ @Test
+ public void serialize_hasSizeTwo_returnSizeTwo() {
+ final List<ContextualCard> cards = new ArrayList<>();
+ cards.add(new ContextualCard.Builder()
+ .setName("name1")
+ .setSliceUri(Uri.parse("uri1"))
+ .build());
+ cards.add(new ContextualCard.Builder()
+ .setName("name2")
+ .setSliceUri(Uri.parse("uri2"))
+ .build());
+
+
+ final byte[] data = mImpl.serialize(cards);
+
+ try {
+ assertThat(ContextualCardList
+ .parseFrom(data).getCardCount()).isEqualTo(cards.size());
+ } catch (Exception e) {
+ throw new RuntimeException(e.getMessage());
+ }
}
}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java b/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java
index f9937f4..821d383 100644
--- a/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java
@@ -505,6 +505,19 @@
assertThat(actualIconResource).isEqualTo(settingsIcon);
}
+ @Test
+ public void getSafeIcon_invalidResource_shouldFallbackToSettingsIcon() {
+ final int settingsIcon = R.drawable.ic_settings;
+ final int badIcon = 0x12345678;
+ final SliceData data = getDummyData(TOGGLE_CONTROLLER, SliceData.SliceType.SWITCH,
+ badIcon);
+
+ final IconCompat actualIcon = SliceBuilderUtils.getSafeIcon(mContext, data);
+
+ final int actualIconResource = actualIcon.toIcon().getResId();
+ assertThat(actualIconResource).isEqualTo(settingsIcon);
+ }
+
private SliceData getDummyData() {
return getDummyData(TOGGLE_CONTROLLER, SUMMARY, SliceData.SliceType.SWITCH, SCREEN_TITLE,
ICON, IS_DYNAMIC_SUMMARY_ALLOWED);
diff --git a/tests/robotests/src/com/android/settings/widget/RadioButtonPickerFragmentTest.java b/tests/robotests/src/com/android/settings/widget/RadioButtonPickerFragmentTest.java
index 64352d9..55d212f 100644
--- a/tests/robotests/src/com/android/settings/widget/RadioButtonPickerFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/widget/RadioButtonPickerFragmentTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -37,7 +38,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
+import org.mockito.InOrder;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
@@ -99,6 +102,26 @@
}
@Test
+ public void staticPreferencesPrepended_addedFirst() {
+ mFragment.mAppendStaticPreferences = false;
+ mFragment.updateCandidates();
+
+ InOrder inOrder = Mockito.inOrder(mFragment);
+ inOrder.verify(mFragment).addStaticPreferences(any());
+ inOrder.verify(mFragment).getRadioButtonPreferenceCustomLayoutResId();
+ }
+
+ @Test
+ public void staticPreferencesAppended_addedLast() {
+ mFragment.mAppendStaticPreferences = true;
+ mFragment.updateCandidates();
+
+ InOrder inOrder = Mockito.inOrder(mFragment);
+ inOrder.verify(mFragment).mayCheckOnlyRadioButton();
+ inOrder.verify(mFragment).addStaticPreferences(any());
+ }
+
+ @Test
public void shouldHaveNoCustomPreferenceLayout() {
assertThat(mFragment.getRadioButtonPreferenceCustomLayoutResId()).isEqualTo(0);
}