diff options
author | calderwoodra <calderwoodra@google.com> | 2017-09-20 14:03:33 -0700 |
---|---|---|
committer | Eric Erfanian <erfanian@google.com> | 2017-09-22 15:55:15 +0000 |
commit | f7d8f63913c8d1e83ee379cbf60077f02c978cad (patch) | |
tree | 8ef43661758ddd74f6e0ec563bc3801006fcdeb5 | |
parent | 81643679f629176a6e79e6b3f08ad0a78f426935 (diff) |
Added location permission request inline the search fragment.
This change adds a prompt inline the search fragment list view
to request the location permission (if they haven't already
granted it).
screenshot: http://screen/upu9t55mghq
Bug: 65858857
Test: NSFT, SAT, SCMT
PiperOrigin-RevId: 169447095
Change-Id: I6c312057ff3c4e2362ce21b0c57e1e5de7b25ce0
4 files changed, 180 insertions, 6 deletions
diff --git a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java index 3cf8fb5b9..1dbd953dd 100644 --- a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java +++ b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java @@ -16,6 +16,8 @@ package com.android.dialer.searchfragment.list; +import static android.Manifest.permission.ACCESS_FINE_LOCATION; + import android.app.Fragment; import android.app.LoaderManager.LoaderCallbacks; import android.content.Loader; @@ -76,6 +78,7 @@ public final class NewSearchFragment extends Fragment private static final String KEY_SHOW_ZERO_SUGGEST = "use_zero_suggest"; @VisibleForTesting public static final int READ_CONTACTS_PERMISSION_REQUEST_CODE = 1; + @VisibleForTesting private static final int LOCATION_PERMISSION_REQUEST_CODE = 2; private static final int CONTACTS_LOADER_ID = 0; private static final int NEARBY_PLACES_LOADER_ID = 1; @@ -279,6 +282,12 @@ public final class NewSearchFragment extends Fragment emptyContentView.setVisibility(View.GONE); initLoaders(); } + } else if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) { + if (grantResults.length >= 1 && PackageManager.PERMISSION_GRANTED == grantResults[0]) { + // Force a refresh of the data since we were missing the permission before this. + loadNearbyPlacesCursor(); + adapter.hideLocationPermissionRequest(); + } } } @@ -317,6 +326,12 @@ public final class NewSearchFragment extends Fragment // Should not be called before remote directories (not contacts) have finished loading. private void loadNearbyPlacesCursor() { + if (!PermissionsUtil.hasLocationPermissions(getContext())) { + if (adapter != null) { + adapter.showLocationPermissionRequest(v -> requestLocationPermission()); + } + return; + } // Cancel existing load if one exists. ThreadUtil.getUiThreadHandler().removeCallbacks(loadNearbyPlacesRunnable); @@ -328,6 +343,16 @@ public final class NewSearchFragment extends Fragment .postDelayed(loadNearbyPlacesRunnable, NETWORK_SEARCH_DELAY_MILLIS); } + private void requestLocationPermission() { + Assert.checkArgument( + !PermissionsUtil.hasPermission(getContext(), ACCESS_FINE_LOCATION), + "attempted to request already granted location permission"); + String[] deniedPermissions = + PermissionsUtil.getPermissionsCurrentlyDenied( + getContext(), PermissionsUtil.allLocationGroupPermissionsUsedInDialer); + requestPermissions(deniedPermissions, LOCATION_PERMISSION_REQUEST_CODE); + } + @Override public void onResume() { super.onResume(); diff --git a/java/com/android/dialer/searchfragment/list/SearchAdapter.java b/java/com/android/dialer/searchfragment/list/SearchAdapter.java index d4b5cf29d..358a59a41 100644 --- a/java/com/android/dialer/searchfragment/list/SearchAdapter.java +++ b/java/com/android/dialer/searchfragment/list/SearchAdapter.java @@ -23,6 +23,8 @@ import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.ViewHolder; import android.text.TextUtils; import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; import android.view.ViewGroup; import com.android.dialer.callcomposer.CallComposerActivity; import com.android.dialer.callintent.CallInitiationType; @@ -54,6 +56,7 @@ public final class SearchAdapter extends RecyclerView.Adapter<ViewHolder> private boolean showZeroSuggest; private String query; private CallInitiationType.Type callInitiationType = CallInitiationType.Type.UNKNOWN_INITIATION; + private OnClickListener locationRequestClickListener; @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) public SearchAdapter(Activity activity, SearchCursorManager searchCursorManager) { @@ -81,6 +84,10 @@ public final class SearchAdapter extends RecyclerView.Adapter<ViewHolder> case RowType.SEARCH_ACTION: return new SearchActionViewHolder( LayoutInflater.from(activity).inflate(R.layout.search_action_layout, root, false)); + case RowType.LOCATION_REQUEST: + return new LocationPermissionViewHolder( + LayoutInflater.from(activity).inflate(R.layout.location_permission_row, root, false), + locationRequestClickListener); case RowType.INVALID: default: throw Assert.createIllegalStateFailException("Invalid RowType: " + rowType); @@ -107,6 +114,8 @@ public final class SearchAdapter extends RecyclerView.Adapter<ViewHolder> } else if (holder instanceof SearchActionViewHolder) { ((SearchActionViewHolder) holder) .setAction(searchCursorManager.getSearchAction(position), position, query); + } else if (holder instanceof LocationPermissionViewHolder) { + // No-op } else { throw Assert.createIllegalStateFailException("Invalid ViewHolder: " + holder); } @@ -165,6 +174,28 @@ public final class SearchAdapter extends RecyclerView.Adapter<ViewHolder> } } + /** + * Updates the adapter to show the location request row element. If the element was previously + * hidden, the adapter will call {@link #notifyDataSetChanged()}. + */ + public void showLocationPermissionRequest(OnClickListener clickListener) { + Assert.isNotNull(locationRequestClickListener = clickListener); + if (searchCursorManager.showLocationPermissionRequest(true)) { + notifyDataSetChanged(); + } + } + + /** + * Updates the adapter to hide the location request row element. If the element was previously + * visible, the adapter will call {@link #notifyDataSetChanged()}. + */ + void hideLocationPermissionRequest() { + locationRequestClickListener = null; + if (searchCursorManager.showLocationPermissionRequest(false)) { + notifyDataSetChanged(); + } + } + public void setRemoteContactsCursor(SearchCursor remoteContactsCursor) { if (searchCursorManager.setCorpDirectoryCursor(remoteContactsCursor)) { notifyDataSetChanged(); @@ -207,4 +238,17 @@ public final class SearchAdapter extends RecyclerView.Adapter<ViewHolder> Intent intent = CallComposerActivity.newIntent(activity, contact); DialerUtils.startActivityWithErrorToast(activity, intent); } + + /** Viewholder for R.layout.location_permission_row that requests the location permission. */ + private static class LocationPermissionViewHolder extends RecyclerView.ViewHolder { + + LocationPermissionViewHolder(View itemView, OnClickListener locationRequestClickListener) { + super(itemView); + Assert.isNotNull(locationRequestClickListener); + itemView + .findViewById( + com.android.dialer.searchfragment.nearbyplaces.R.id.location_permission_allow) + .setOnClickListener(locationRequestClickListener); + } + } } diff --git a/java/com/android/dialer/searchfragment/list/SearchCursorManager.java b/java/com/android/dialer/searchfragment/list/SearchCursorManager.java index 3704e817d..f8d1e1be5 100644 --- a/java/com/android/dialer/searchfragment/list/SearchCursorManager.java +++ b/java/com/android/dialer/searchfragment/list/SearchCursorManager.java @@ -16,6 +16,7 @@ package com.android.dialer.searchfragment.list; +import android.database.MatrixCursor; import android.support.annotation.IntDef; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; @@ -60,7 +61,8 @@ public final class SearchCursorManager { SearchCursorManager.RowType.NEARBY_PLACES_ROW, SearchCursorManager.RowType.DIRECTORY_HEADER, SearchCursorManager.RowType.DIRECTORY_ROW, - SearchCursorManager.RowType.SEARCH_ACTION + SearchCursorManager.RowType.SEARCH_ACTION, + SearchCursorManager.RowType.LOCATION_REQUEST }) @interface RowType { int INVALID = 0; @@ -79,13 +81,20 @@ public final class SearchCursorManager { int DIRECTORY_ROW = 6; /** A row containing a search action */ int SEARCH_ACTION = 7; + /** A row which requests location permission */ + int LOCATION_REQUEST = 8; } + private static final LocationPermissionCursor LOCATION_PERMISSION_CURSOR = + new LocationPermissionCursor(new String[0]); + private SearchCursor contactsCursor = null; private SearchCursor nearbyPlacesCursor = null; private SearchCursor corpDirectoryCursor = null; private List<Integer> searchActions = new ArrayList<>(); + private boolean showLocationPermissionRequest; + /** Returns true if the cursor changed. */ boolean setContactsCursor(@Nullable SearchCursor cursor) { if (cursor == contactsCursor) { @@ -122,6 +131,15 @@ public final class SearchCursorManager { return true; } + /** Returns true if the value changed. */ + boolean showLocationPermissionRequest(boolean enabled) { + if (showLocationPermissionRequest == enabled) { + return false; + } + showLocationPermissionRequest = enabled; + return true; + } + /** Returns true if a cursor changed. */ boolean setCorpDirectoryCursor(@Nullable SearchCursor cursor) { if (cursor == corpDirectoryCursor) { @@ -177,7 +195,9 @@ public final class SearchCursorManager { count += contactsCursor.getCount(); } - if (nearbyPlacesCursor != null) { + if (showLocationPermissionRequest) { + count++; + } else if (nearbyPlacesCursor != null) { count += nearbyPlacesCursor.getCount(); } @@ -203,6 +223,10 @@ public final class SearchCursorManager { return cursor.isHeader() ? RowType.CONTACT_HEADER : RowType.CONTACT_ROW; } + if (cursor == LOCATION_PERMISSION_CURSOR) { + return RowType.LOCATION_REQUEST; + } + if (cursor == nearbyPlacesCursor) { return cursor.isHeader() ? RowType.NEARBY_PLACES_HEADER : RowType.NEARBY_PLACES_ROW; } @@ -214,9 +238,7 @@ public final class SearchCursorManager { } /** - * Gets cursor corresponding to position in coalesced list of search cursors. Callers should - * ensure that {@link #getRowType(int)} doesn't correspond to header position, otherwise an - * exception will be thrown. + * Gets cursor corresponding to position in coalesced list of search cursors. * * @param position in coalesced list of search cursors * @return Cursor moved to position specific to passed in position. @@ -232,7 +254,13 @@ public final class SearchCursorManager { position -= count; } - if (nearbyPlacesCursor != null) { + if (showLocationPermissionRequest) { + if (position == 0) { + return LOCATION_PERMISSION_CURSOR; + } + position--; + + } else if (nearbyPlacesCursor != null) { int count = nearbyPlacesCursor.getCount(); if (position - count < 0) { @@ -272,4 +300,30 @@ public final class SearchCursorManager { corpDirectoryCursor = null; } } + + /** + * No-op implementation of {@link android.database.Cursor} and {@link SearchCursor} for + * representing location permission request row elements. + */ + private static class LocationPermissionCursor extends MatrixCursor implements SearchCursor { + + LocationPermissionCursor(String[] columnNames) { + super(columnNames); + } + + @Override + public boolean isHeader() { + return false; + } + + @Override + public boolean updateQuery(String query) { + return false; + } + + @Override + public long getDirectoryId() { + return 0; + } + } } diff --git a/java/com/android/dialer/searchfragment/nearbyplaces/res/layout/location_permission_row.xml b/java/com/android/dialer/searchfragment/nearbyplaces/res/layout/location_permission_row.xml new file mode 100644 index 000000000..800bf62a8 --- /dev/null +++ b/java/com/android/dialer/searchfragment/nearbyplaces/res/layout/location_permission_row.xml @@ -0,0 +1,51 @@ +<?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 + --> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingStart="16dp" + android:paddingEnd="16dp" + android:paddingTop="16dp"> + + <ImageView + android:id="@+id/permission_image" + android:layout_width="56dp" + android:layout_height="56dp" + android:layout_marginEnd="16dp" + android:src="@drawable/quantum_ic_my_location_vd_theme_24" + android:tint="@color/dialer_secondary_text_color"/> + + <TextView + android:id="@+id/permission_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_toEndOf="@id/permission_image" + android:minHeight="56dp" + android:text="@string/permission_no_location_for_search" + android:textSize="16sp" + android:textColor="@color/dialer_secondary_text_color"/> + + <Button + android:id="@+id/location_permission_allow" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_below="@id/permission_text" + android:text="@string/nearby_places_allow" + android:textColor="@color/dialer_theme_color" + style="@style/Widget.AppCompat.Button.Borderless"/> +</RelativeLayout>
\ No newline at end of file |