summaryrefslogtreecommitdiffstats
path: root/java/com/android/dialer/searchfragment/list
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/dialer/searchfragment/list')
-rw-r--r--java/com/android/dialer/searchfragment/list/AndroidManifest.xml16
-rw-r--r--java/com/android/dialer/searchfragment/list/HeaderViewHolder.java36
-rw-r--r--java/com/android/dialer/searchfragment/list/NewSearchFragment.java125
-rw-r--r--java/com/android/dialer/searchfragment/list/SearchAdapter.java108
-rw-r--r--java/com/android/dialer/searchfragment/list/SearchCursorManager.java273
-rw-r--r--java/com/android/dialer/searchfragment/list/res/layout/fragment_search.xml21
-rw-r--r--java/com/android/dialer/searchfragment/list/res/layout/header_layout.xml22
7 files changed, 601 insertions, 0 deletions
diff --git a/java/com/android/dialer/searchfragment/list/AndroidManifest.xml b/java/com/android/dialer/searchfragment/list/AndroidManifest.xml
new file mode 100644
index 000000000..e0890cc2c
--- /dev/null
+++ b/java/com/android/dialer/searchfragment/list/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<!--
+ ~ 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
+ -->
+<manifest package="com.android.dialer.searchfragment.list"/> \ No newline at end of file
diff --git a/java/com/android/dialer/searchfragment/list/HeaderViewHolder.java b/java/com/android/dialer/searchfragment/list/HeaderViewHolder.java
new file mode 100644
index 000000000..dd35b13ee
--- /dev/null
+++ b/java/com/android/dialer/searchfragment/list/HeaderViewHolder.java
@@ -0,0 +1,36 @@
+/*
+ * 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.dialer.searchfragment.list;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.widget.TextView;
+
+/** ViewHolder for header rows in {@link NewSearchFragment}. */
+final class HeaderViewHolder extends RecyclerView.ViewHolder {
+
+ private final TextView header;
+
+ HeaderViewHolder(View view) {
+ super(view);
+ header = view.findViewById(R.id.header);
+ }
+
+ public void setHeader(String header) {
+ this.header.setText(header);
+ }
+}
diff --git a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
new file mode 100644
index 000000000..fcc87c386
--- /dev/null
+++ b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
@@ -0,0 +1,125 @@
+/*
+ * 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.dialer.searchfragment.list;
+
+import android.app.Fragment;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.Loader;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import com.android.contacts.common.extensions.PhoneDirectoryExtenderAccessor;
+import com.android.dialer.common.concurrent.ThreadUtil;
+import com.android.dialer.searchfragment.cp2.SearchContactsCursorLoader;
+import com.android.dialer.searchfragment.nearbyplaces.NearbyPlacesCursorLoader;
+
+/** Fragment used for searching contacts. */
+public final class NewSearchFragment extends Fragment implements LoaderCallbacks<Cursor> {
+
+ // Since some of our queries can generate network requests, we should delay them until the user
+ // stops typing to prevent generating too much network traffic.
+ private static final int NETWORK_SEARCH_DELAY_MILLIS = 300;
+
+ private static final int CONTACTS_LOADER_ID = 0;
+ private static final int NEARBY_PLACES_ID = 1;
+
+ private RecyclerView recyclerView;
+ private SearchAdapter adapter;
+ private String query;
+
+ private final Runnable loadNearbyPlacesRunnable =
+ () -> getLoaderManager().restartLoader(NEARBY_PLACES_ID, null, this);
+
+ @Nullable
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, @Nullable ViewGroup parent, @Nullable Bundle bundle) {
+ getLoaderManager().initLoader(0, null, this);
+ View view = inflater.inflate(R.layout.fragment_search, parent, false);
+ adapter = new SearchAdapter(getContext());
+ recyclerView = view.findViewById(R.id.recycler_view);
+ recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
+ recyclerView.setAdapter(adapter);
+
+ getLoaderManager().initLoader(CONTACTS_LOADER_ID, null, this);
+ loadNearbyPlacesCursor();
+ return view;
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle bundle) {
+ // TODO add enterprise loader
+ if (id == CONTACTS_LOADER_ID) {
+ return new SearchContactsCursorLoader(getContext());
+ } else if (id == NEARBY_PLACES_ID) {
+ return new NearbyPlacesCursorLoader(getContext(), query);
+ } else {
+ throw new IllegalStateException("Invalid loader id: " + id);
+ }
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+ if (loader instanceof SearchContactsCursorLoader) {
+ adapter.setContactsCursor(cursor);
+ } else if (loader instanceof NearbyPlacesCursorLoader) {
+ adapter.setNearbyPlacesCursor(cursor);
+ } else {
+ throw new IllegalStateException("Invalid loader: " + loader);
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ adapter.clear();
+ recyclerView.setAdapter(null);
+ }
+
+ public void setQuery(String query) {
+ this.query = query;
+ if (adapter != null) {
+ adapter.setQuery(query);
+ loadNearbyPlacesCursor();
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ // close adapters
+ adapter.setNearbyPlacesCursor(null);
+ adapter.setContactsCursor(null);
+ ThreadUtil.getUiThreadHandler().removeCallbacks(loadNearbyPlacesRunnable);
+ }
+
+ private void loadNearbyPlacesCursor() {
+ // Cancel existing load if one exists.
+ ThreadUtil.getUiThreadHandler().removeCallbacks(loadNearbyPlacesRunnable);
+
+ // If nearby places is not enabled, do not try to load them.
+ if (!PhoneDirectoryExtenderAccessor.get(getContext()).isEnabled(getContext())) {
+ return;
+ }
+ ThreadUtil.getUiThreadHandler()
+ .postDelayed(loadNearbyPlacesRunnable, NETWORK_SEARCH_DELAY_MILLIS);
+ }
+}
diff --git a/java/com/android/dialer/searchfragment/list/SearchAdapter.java b/java/com/android/dialer/searchfragment/list/SearchAdapter.java
new file mode 100644
index 000000000..023513e47
--- /dev/null
+++ b/java/com/android/dialer/searchfragment/list/SearchAdapter.java
@@ -0,0 +1,108 @@
+/*
+ * 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.dialer.searchfragment.list;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import com.android.dialer.common.Assert;
+import com.android.dialer.searchfragment.cp2.SearchContactViewHolder;
+import com.android.dialer.searchfragment.list.SearchCursorManager.RowType;
+import com.android.dialer.searchfragment.nearbyplaces.NearbyPlaceViewHolder;
+
+/** RecyclerView adapter for {@link NewSearchFragment}. */
+class SearchAdapter extends RecyclerView.Adapter<ViewHolder> {
+
+ private final SearchCursorManager searchCursorManager;
+ private final Context context;
+
+ private String query;
+
+ SearchAdapter(Context context) {
+ searchCursorManager = new SearchCursorManager();
+ this.context = context;
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup root, @RowType int rowType) {
+ switch (rowType) {
+ case RowType.CONTACT_ROW:
+ return new SearchContactViewHolder(
+ LayoutInflater.from(context).inflate(R.layout.search_contact_row, root, false));
+ case RowType.NEARBY_PLACES_ROW:
+ return new NearbyPlaceViewHolder(
+ LayoutInflater.from(context).inflate(R.layout.search_contact_row, root, false));
+ case RowType.DIRECTORY_HEADER:
+ case RowType.NEARBY_PLACES_HEADER:
+ return new HeaderViewHolder(
+ LayoutInflater.from(context).inflate(R.layout.header_layout, root, false));
+ case RowType.DIRECTORY_ROW: // TODO: add directory rows to search
+ case RowType.INVALID:
+ default:
+ throw Assert.createIllegalStateFailException("Invalid RowType: " + rowType);
+ }
+ }
+
+ @Override
+ public @RowType int getItemViewType(int position) {
+ return searchCursorManager.getRowType(position);
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ if (holder instanceof SearchContactViewHolder) {
+ Cursor cursor = searchCursorManager.getCursor(position);
+ ((SearchContactViewHolder) holder).bind(cursor, query);
+ } else if (holder instanceof NearbyPlaceViewHolder) {
+ Cursor cursor = searchCursorManager.getCursor(position);
+ ((NearbyPlaceViewHolder) holder).bind(cursor, query);
+ } else if (holder instanceof HeaderViewHolder) {
+ String header = context.getString(searchCursorManager.getHeaderText(position));
+ ((HeaderViewHolder) holder).setHeader(header);
+ } else {
+ throw Assert.createIllegalStateFailException("Invalid ViewHolder: " + holder);
+ }
+ }
+
+ void setContactsCursor(Cursor cursor) {
+ searchCursorManager.setContactsCursor(cursor);
+ notifyDataSetChanged();
+ }
+
+ void clear() {
+ searchCursorManager.clear();
+ }
+
+ @Override
+ public int getItemCount() {
+ return searchCursorManager.getCount();
+ }
+
+ public void setQuery(String query) {
+ this.query = query;
+ searchCursorManager.setQuery(query);
+ notifyDataSetChanged();
+ }
+
+ public void setNearbyPlacesCursor(Cursor nearbyPlacesCursor) {
+ searchCursorManager.setNearbyPlacesCursor(nearbyPlacesCursor);
+ notifyDataSetChanged();
+ }
+}
diff --git a/java/com/android/dialer/searchfragment/list/SearchCursorManager.java b/java/com/android/dialer/searchfragment/list/SearchCursorManager.java
new file mode 100644
index 000000000..216a9ada9
--- /dev/null
+++ b/java/com/android/dialer/searchfragment/list/SearchCursorManager.java
@@ -0,0 +1,273 @@
+/*
+ * 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.dialer.searchfragment.list;
+
+import android.database.Cursor;
+import android.support.annotation.IntDef;
+import android.support.annotation.StringRes;
+import com.android.dialer.common.Assert;
+import com.android.dialer.searchfragment.cp2.SearchContactCursor;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Manages all of the cursors needed for {@link SearchAdapter}.
+ *
+ * <p>This class accepts three cursors:
+ *
+ * <ul>
+ * <li>A contacts cursor {@link #setContactsCursor(Cursor)}
+ * <li>A google search results cursor {@link #setNearbyPlacesCursor(Cursor)}
+ * <li>A work directory cursor {@link #setCorpDirectoryCursor(Cursor)}
+ * </ul>
+ *
+ * <p>The key purpose of this class is to compose three aforementioned cursors together to function
+ * as one cursor. The key methods needed to utilize this class as a cursor are:
+ *
+ * <ul>
+ * <li>{@link #getCursor(int)}
+ * <li>{@link #getCount()}
+ * <li>{@link #getRowType(int)}
+ * </ul>
+ */
+final class SearchCursorManager {
+
+ /** IntDef for the different types of rows that can be shown when searching. */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ SearchCursorManager.RowType.INVALID,
+ SearchCursorManager.RowType.CONTACT_ROW,
+ SearchCursorManager.RowType.NEARBY_PLACES_HEADER,
+ SearchCursorManager.RowType.NEARBY_PLACES_ROW,
+ SearchCursorManager.RowType.DIRECTORY_HEADER,
+ SearchCursorManager.RowType.DIRECTORY_ROW
+ })
+ @interface RowType {
+ int INVALID = 0;
+ /** A row containing contact information for contacts stored locally on device. */
+ int CONTACT_ROW = 1;
+ /** Header to mark the end of contact rows and start of nearby places rows. */
+ int NEARBY_PLACES_HEADER = 2;
+ /** A row containing nearby places information/search results. */
+ int NEARBY_PLACES_ROW = 3;
+ /** Header to mark the end of the previous row set and start of directory rows. */
+ int DIRECTORY_HEADER = 4;
+ /** A row containing contact information for contacts stored externally in corp directories. */
+ int DIRECTORY_ROW = 5;
+ }
+
+ private Cursor contactsCursor = null;
+ private Cursor nearbyPlacesCursor = null;
+ private Cursor corpDirectoryCursor = null;
+
+ void setContactsCursor(Cursor cursor) {
+ if (cursor == contactsCursor) {
+ return;
+ }
+
+ if (contactsCursor != null && !contactsCursor.isClosed()) {
+ contactsCursor.close();
+ }
+
+ if (cursor != null && cursor.getCount() > 0) {
+ contactsCursor = cursor;
+ } else {
+ contactsCursor = null;
+ }
+ }
+
+ void setNearbyPlacesCursor(Cursor cursor) {
+ if (cursor == nearbyPlacesCursor) {
+ return;
+ }
+
+ if (nearbyPlacesCursor != null && !nearbyPlacesCursor.isClosed()) {
+ nearbyPlacesCursor.close();
+ }
+
+ if (cursor != null && cursor.getCount() > 0) {
+ nearbyPlacesCursor = cursor;
+ } else {
+ nearbyPlacesCursor = null;
+ }
+ }
+
+ void setCorpDirectoryCursor(Cursor cursor) {
+ if (cursor == corpDirectoryCursor) {
+ return;
+ }
+
+ if (corpDirectoryCursor != null && !corpDirectoryCursor.isClosed()) {
+ corpDirectoryCursor.close();
+ }
+
+ if (cursor != null && cursor.getCount() > 0) {
+ corpDirectoryCursor = cursor;
+ } else {
+ corpDirectoryCursor = null;
+ }
+ }
+
+ void setQuery(String query) {
+ if (contactsCursor != null) {
+ // TODO: abstract this
+ ((SearchContactCursor) contactsCursor).filter(query);
+ }
+ }
+
+ /** @return the sum of counts of all cursors, including headers. */
+ int getCount() {
+ int count = 0;
+ if (contactsCursor != null) {
+ count += contactsCursor.getCount();
+ }
+
+ if (nearbyPlacesCursor != null) {
+ count++; // header
+ count += nearbyPlacesCursor.getCount();
+ }
+
+ if (corpDirectoryCursor != null) {
+ count++; // header
+ count += corpDirectoryCursor.getCount();
+ }
+
+ return count;
+ }
+
+ @RowType
+ int getRowType(int position) {
+ if (contactsCursor != null) {
+ position -= contactsCursor.getCount();
+
+ if (position < 0) {
+ return SearchCursorManager.RowType.CONTACT_ROW;
+ }
+ }
+
+ if (nearbyPlacesCursor != null) {
+ if (position == 0) {
+ return SearchCursorManager.RowType.NEARBY_PLACES_HEADER;
+ } else {
+ position--; // header
+ }
+
+ position -= nearbyPlacesCursor.getCount();
+
+ if (position < 0) {
+ return SearchCursorManager.RowType.NEARBY_PLACES_ROW;
+ }
+ }
+
+ if (corpDirectoryCursor != null) {
+ if (position == 0) {
+ return SearchCursorManager.RowType.DIRECTORY_HEADER;
+ } else {
+ position--; // header
+ }
+
+ position -= corpDirectoryCursor.getCount();
+
+ if (position < 0) {
+ return SearchCursorManager.RowType.DIRECTORY_ROW;
+ }
+ }
+
+ throw Assert.createIllegalStateFailException("No valid row type.");
+ }
+
+ /**
+ * Gets cursor corresponding to position in coelesced list of search cursors. Callers should
+ * ensure that {@link #getRowType(int)} doesn't correspond to header position, otherwise an
+ * exception will be thrown.
+ *
+ * @param position in coalecsed list of search cursors
+ * @return Cursor moved to position specific to passed in position.
+ */
+ Cursor getCursor(int position) {
+ if (contactsCursor != null) {
+ int count = contactsCursor.getCount();
+
+ if (position - count < 0) {
+ contactsCursor.moveToPosition(position);
+ return contactsCursor;
+ }
+ position -= count;
+ }
+
+ if (nearbyPlacesCursor != null) {
+ Assert.checkArgument(position != 0, "No valid cursor, position is nearby places header.");
+ position--; // header
+ int count = nearbyPlacesCursor.getCount();
+
+ if (position - count < 0) {
+ nearbyPlacesCursor.moveToPosition(position);
+ return nearbyPlacesCursor;
+ }
+ position -= count;
+ }
+
+ if (corpDirectoryCursor != null) {
+ Assert.checkArgument(position != 0, "No valid cursor, position is directory search header.");
+ position--; // header
+ int count = corpDirectoryCursor.getCount();
+
+ if (position - count < 0) {
+ corpDirectoryCursor.moveToPosition(position);
+ return corpDirectoryCursor;
+ }
+ position -= count;
+ }
+
+ throw Assert.createIllegalStateFailException("No valid cursor.");
+ }
+
+ @StringRes
+ int getHeaderText(int position) {
+ @RowType int rowType = getRowType(position);
+ switch (rowType) {
+ case RowType.NEARBY_PLACES_HEADER:
+ return R.string.nearby_places;
+ case RowType.DIRECTORY_HEADER: // TODO
+ case RowType.DIRECTORY_ROW:
+ case RowType.CONTACT_ROW:
+ case RowType.NEARBY_PLACES_ROW:
+ case RowType.INVALID:
+ default:
+ throw Assert.createIllegalStateFailException(
+ "Invalid row type, position " + position + " is rowtype " + rowType);
+ }
+ }
+
+ /** removes all cursors. */
+ void clear() {
+ if (contactsCursor != null) {
+ contactsCursor.close();
+ contactsCursor = null;
+ }
+
+ if (nearbyPlacesCursor != null) {
+ nearbyPlacesCursor.close();
+ nearbyPlacesCursor = null;
+ }
+
+ if (corpDirectoryCursor != null) {
+ corpDirectoryCursor.close();
+ corpDirectoryCursor = null;
+ }
+ }
+}
diff --git a/java/com/android/dialer/searchfragment/list/res/layout/fragment_search.xml b/java/com/android/dialer/searchfragment/list/res/layout/fragment_search.xml
new file mode 100644
index 000000000..06f234889
--- /dev/null
+++ b/java/com/android/dialer/searchfragment/list/res/layout/fragment_search.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<android.support.v7.widget.RecyclerView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/recycler_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
diff --git a/java/com/android/dialer/searchfragment/list/res/layout/header_layout.xml b/java/com/android/dialer/searchfragment/list/res/layout/header_layout.xml
new file mode 100644
index 000000000..36af42ed9
--- /dev/null
+++ b/java/com/android/dialer/searchfragment/list/res/layout/header_layout.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/header"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingStart="16dp"
+ style="@style/SecondaryText"/>