diff options
Diffstat (limited to 'src')
15 files changed, 526 insertions, 118 deletions
diff --git a/src/com/android/dialer/DialtactsActivity.java b/src/com/android/dialer/DialtactsActivity.java index 31d1b94bf..98f34b57d 100644 --- a/src/com/android/dialer/DialtactsActivity.java +++ b/src/com/android/dialer/DialtactsActivity.java @@ -66,6 +66,7 @@ import com.android.contacts.common.util.PermissionsUtil; import com.android.contacts.common.widget.FloatingActionButtonController; import com.android.contacts.commonbind.analytics.AnalyticsUtil; import com.android.dialer.calllog.CallLogActivity; +import com.android.dialer.calllog.CallLogFragment; import com.android.dialer.database.DialerDatabaseHelper; import com.android.dialer.dialpad.DialpadFragment; import com.android.dialer.dialpad.SmartDialNameMatcher; @@ -101,6 +102,8 @@ import java.util.List; public class DialtactsActivity extends TransactionSafeActivity implements View.OnClickListener, DialpadFragment.OnDialpadQueryChangedListener, OnListFragmentScrolledListener, + CallLogFragment.HostInterface, + DialpadFragment.HostInterface, ListsFragment.HostInterface, SpeedDialFragment.HostInterface, SearchFragment.HostInterface, @@ -484,8 +487,6 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O } }); - setupActivityOverlay(); - Trace.endSection(); Trace.beginSection(TAG + " initialize smart dialing"); @@ -495,19 +496,6 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O Trace.endSection(); } - private void setupActivityOverlay() { - final View activityOverlay = findViewById(R.id.activity_overlay); - activityOverlay.setOnTouchListener(new OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - if (!mIsDialpadShown) { - maybeExitSearchUi(); - } - return false; - } - }); - } - @Override protected void onResume() { Trace.beginSection(TAG + " onResume"); @@ -1145,7 +1133,16 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O } catch (Exception ignored) { // Skip any exceptions for this piece of code } + } + @Override + public boolean onDialpadSpacerTouchWithEmptyQuery() { + if (mInDialpadSearch && mSmartDialSearchFragment != null + && !mSmartDialSearchFragment.isShowingPermissionRequest()) { + hideDialpadFragment(true /* animate */, true /* clearDialpad */); + return true; + } + return false; } @Override @@ -1207,6 +1204,24 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O mListsFragment.getRemoveView().setDragDropController(dragController); } + /** + * Implemented to satisfy {@link SpeedDialFragment.HostInterface} + */ + @Override + public void showAllContactsTab() { + if (mListsFragment != null) { + mListsFragment.showTab(ListsFragment.TAB_INDEX_ALL_CONTACTS); + } + } + + /** + * Implemented to satisfy {@link CallLogFragment.HostInterface} + */ + @Override + public void showDialpad() { + showDialpadFragment(true); + } + @Override public void onPickPhoneNumberAction(Uri dataUri) { // Specify call-origin so that users will see the previous tab instead of @@ -1322,7 +1337,6 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O return mActionBarHeight; } - private int getFabAlignment() { if (!mIsLandscape && !isInSearchUi() && mListsFragment.getCurrentTabIndex() == ListsFragment.TAB_INDEX_SPEED_DIAL) { diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java index 00e9ea016..9609e4d6f 100644 --- a/src/com/android/dialer/calllog/CallLogAdapter.java +++ b/src/com/android/dialer/calllog/CallLogAdapter.java @@ -65,7 +65,9 @@ import java.util.HashMap; * Adapter class to fill in data for the Call Log. */ public class CallLogAdapter extends GroupingListAdapter - implements ViewTreeObserver.OnPreDrawListener, CallLogGroupBuilder.GroupCreator { + implements ViewTreeObserver.OnPreDrawListener, + CallLogGroupBuilder.GroupCreator, + VoicemailPlaybackPresenter.OnVoicemailDeletedListener { /** Interface used to initiate a refresh of the content. */ public interface CallFetcher { @@ -314,6 +316,9 @@ public class CallLogAdapter extends GroupingListAdapter mCallFetcher = callFetcher; mContactInfoHelper = contactInfoHelper; mVoicemailPlaybackPresenter = voicemailPlaybackPresenter; + if (mVoicemailPlaybackPresenter != null) { + mVoicemailPlaybackPresenter.setOnVoicemailDeletedListener(this); + } mIsShowingRecentsTab = isShowingRecentsTab; mContactInfoCache = new ContactInfoCache( @@ -619,6 +624,12 @@ public class CallLogAdapter extends GroupingListAdapter return mIsShowingRecentsTab; } + @Override + public void onVoicemailDeleted(Uri uri) { + mCurrentlyExpandedRowId = NO_EXPANDED_LIST_ITEM; + mCurrentlyExpandedPosition = RecyclerView.NO_POSITION; + } + /** * Retrieves the day group of the previous call in the call log. Used to determine if the day * group has changed and to trigger display of the day group text. diff --git a/src/com/android/dialer/calllog/CallLogFragment.java b/src/com/android/dialer/calllog/CallLogFragment.java index 5d7c408ce..59e2c7f38 100644 --- a/src/com/android/dialer/calllog/CallLogFragment.java +++ b/src/com/android/dialer/calllog/CallLogFragment.java @@ -16,6 +16,8 @@ package com.android.dialer.calllog; +import static android.Manifest.permission.READ_CALL_LOG; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; @@ -26,6 +28,7 @@ import android.app.KeyguardManager; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.database.ContentObserver; import android.database.Cursor; import android.graphics.Rect; @@ -46,6 +49,7 @@ import android.widget.ListView; import android.widget.TextView; import com.android.contacts.common.GeoUtil; +import com.android.contacts.common.util.PermissionsUtil; import com.android.contacts.common.util.ViewUtil; import com.android.dialer.R; import com.android.dialer.list.ListsFragment.HostInterface; @@ -55,6 +59,8 @@ import com.android.dialer.voicemail.VoicemailPlaybackPresenter; import com.android.dialer.voicemail.VoicemailStatusHelper; import com.android.dialer.voicemail.VoicemailStatusHelper.StatusMessage; import com.android.dialer.voicemail.VoicemailStatusHelperImpl; +import com.android.dialer.widget.EmptyContentView; +import com.android.dialer.widget.EmptyContentView.OnEmptyViewActionButtonClickedListener; import com.android.dialerbind.ObjectFactory; import java.util.List; @@ -63,8 +69,8 @@ import java.util.List; * Displays a list of call log entries. To filter for a particular kind of call * (all, missed or voicemails), specify it in the constructor. */ -public class CallLogFragment extends Fragment - implements CallLogQueryHandler.Listener, CallLogAdapter.CallFetcher { +public class CallLogFragment extends Fragment implements CallLogQueryHandler.Listener, + CallLogAdapter.CallFetcher, OnEmptyViewActionButtonClickedListener { private static final String TAG = "CallLogFragment"; /** @@ -81,6 +87,8 @@ public class CallLogFragment extends Fragment // No date-based filtering. private static final int NO_DATE_LIMIT = 0; + private static final int READ_CALL_LOG_PERMISSION_REQUEST_CODE = 1; + private RecyclerView mRecyclerView; private LinearLayoutManager mLayoutManager; private CallLogAdapter mAdapter; @@ -91,7 +99,7 @@ public class CallLogFragment extends Fragment /** Whether there is at least one voicemail source installed. */ private boolean mVoicemailSourcesAvailable = false; - private View mEmptyListView; + private EmptyContentView mEmptyListView; private KeyguardManager mKeyguardManager; private boolean mEmptyLoaderRunning; @@ -116,6 +124,8 @@ public class CallLogFragment extends Fragment private final ContentObserver mVoicemailStatusObserver = new CustomContentObserver(); private boolean mRefreshDataRequired = true; + private boolean mHasReadCallLogPermission = false; + // Exactly same variable is in Fragment as a package private. private boolean mMenuVisible = true; @@ -130,6 +140,16 @@ public class CallLogFragment extends Fragment // the date filter are included. If zero, no date-based filtering occurs. private long mDateLimit = NO_DATE_LIMIT; + /* + * True if this instance of the CallLogFragment is the Recents screen shown in + * DialtactsActivity. + */ + private boolean mIsRecentsFragment; + + public interface HostInterface { + public void showDialpad(); + } + public CallLogFragment() { this(CallLogQueryHandler.CALL_TYPE_ALL, NO_LOG_LIMIT); } @@ -139,9 +159,7 @@ public class CallLogFragment extends Fragment } public CallLogFragment(int filterType, int logLimit) { - super(); - mCallTypeFilter = filterType; - mLogLimit = logLimit; + this(filterType, logLimit, NO_DATE_LIMIT); } /** @@ -162,7 +180,8 @@ public class CallLogFragment extends Fragment * @param dateLimit limits results to calls occurring on or after the specified date. */ public CallLogFragment(int filterType, int logLimit, long dateLimit) { - this(filterType, logLimit); + mCallTypeFilter = filterType; + mLogLimit = logLimit; mDateLimit = dateLimit; } @@ -175,6 +194,8 @@ public class CallLogFragment extends Fragment mDateLimit = state.getLong(KEY_DATE_LIMIT, mDateLimit); } + mIsRecentsFragment = mLogLimit != NO_LOG_LIMIT; + final Activity activity = getActivity(); final ContentResolver resolver = activity.getContentResolver(); String currentCountryIso = GeoUtil.getCurrentCountryIso(activity); @@ -268,7 +289,9 @@ public class CallLogFragment extends Fragment mRecyclerView.setHasFixedSize(true); mLayoutManager = new LinearLayoutManager(getActivity()); mRecyclerView.setLayoutManager(mLayoutManager); - mEmptyListView = view.findViewById(R.id.empty_list_view); + mEmptyListView = (EmptyContentView) view.findViewById(R.id.empty_list_view); + mEmptyListView.setImage(R.drawable.empty_call_log); + mEmptyListView.setActionClickedListener(this); String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity()); boolean isShowingRecentsTab = mLogLimit != NO_LOG_LIMIT || mDateLimit != NO_DATE_LIMIT; @@ -287,7 +310,6 @@ public class CallLogFragment extends Fragment @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - updateEmptyMessage(mCallTypeFilter); mAdapter.onRestoreInstanceState(savedInstanceState); } @@ -305,6 +327,16 @@ public class CallLogFragment extends Fragment @Override public void onResume() { super.onResume(); + final boolean hasReadCallLogPermission = + PermissionsUtil.hasPermission(getActivity(), READ_CALL_LOG); + if (!mHasReadCallLogPermission && hasReadCallLogPermission) { + // We didn't have the permission before, and now we do. Force a refresh of the call log. + // Note that this code path always happens on a fresh start, but mRefreshDataRequired + // is already true in that case anyway. + mRefreshDataRequired = true; + updateEmptyMessage(mCallTypeFilter); + } + mHasReadCallLogPermission = hasReadCallLogPermission; refreshData(); } @@ -359,6 +391,17 @@ public class CallLogFragment extends Fragment } private void updateEmptyMessage(int filterType) { + final Context context = getActivity(); + if (context == null) { + return; + } + + if (!PermissionsUtil.hasPermission(context, READ_CALL_LOG)) { + mEmptyListView.setDescription(R.string.permission_no_calllog); + mEmptyListView.setActionLabel(R.string.permission_single_turn_on); + return; + } + final int messageId; switch (filterType) { case Calls.MISSED_TYPE: @@ -374,8 +417,12 @@ public class CallLogFragment extends Fragment throw new IllegalArgumentException("Unexpected filter type in CallLogFragment: " + filterType); } - DialerUtils.configureEmptyListView( - mEmptyListView, R.drawable.empty_call_log, messageId, getResources()); + mEmptyListView.setDescription(messageId); + if (mIsRecentsFragment) { + mEmptyListView.setActionLabel(R.string.recentCalls_empty_action); + } else { + mEmptyListView.setActionLabel(EmptyContentView.NO_LABEL); + } } CallLogAdapter getAdapter() { @@ -437,4 +484,30 @@ public class CallLogFragment extends Fragment CallLogNotificationsHelper.updateVoicemailNotifications(getActivity()); } } + + @Override + public void onEmptyViewActionButtonClicked() { + final Activity activity = getActivity(); + if (activity == null) { + return; + } + + if (!PermissionsUtil.hasPermission(activity, READ_CALL_LOG)) { + requestPermissions(new String[] {READ_CALL_LOG}, READ_CALL_LOG_PERMISSION_REQUEST_CODE); + } else if (mIsRecentsFragment) { + // Show dialpad if we are the recents fragment. + ((HostInterface) activity).showDialpad(); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, + int[] grantResults) { + if (requestCode == READ_CALL_LOG_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. + mRefreshDataRequired = true; + } + } + } } diff --git a/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java b/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java index d0553b4ac..a6d165e3a 100644 --- a/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java +++ b/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java @@ -302,6 +302,9 @@ public class DefaultVoicemailNotifier { newCalls[cursor.getPosition()] = createNewCallsFromCursor(cursor); } return newCalls; + } catch (RuntimeException e) { + Log.w(TAG, "Exception when querying Contacts Provider for calls lookup"); + return null; } finally { MoreCloseables.closeQuietly(cursor); } @@ -371,6 +374,9 @@ public class DefaultVoicemailNotifier { PROJECTION, null, null, null); if (cursor == null || !cursor.moveToFirst()) return null; return cursor.getString(DISPLAY_NAME_COLUMN_INDEX); + } catch (RuntimeException e) { + Log.w(TAG, "Exception when querying Contacts Provider for name lookup"); + return null; } finally { if (cursor != null) { cursor.close(); diff --git a/src/com/android/dialer/dialpad/DialpadFragment.java b/src/com/android/dialer/dialpad/DialpadFragment.java index b18069fdd..d35abd75b 100644 --- a/src/com/android/dialer/dialpad/DialpadFragment.java +++ b/src/com/android/dialer/dialpad/DialpadFragment.java @@ -135,6 +135,15 @@ public class DialpadFragment extends Fragment void onDialpadQueryChanged(String query); } + public interface HostInterface { + /** + * Notifies the parent activity that the space above the dialpad has been tapped with + * no query in the dialpad present. In most situations this will cause the dialpad to + * be dismissed, unless there happens to be content showing. + */ + boolean onDialpadSpacerTouchWithEmptyQuery(); + } + private static final boolean DEBUG = DialtactsActivity.DEBUG; // This is the amount of screen the dialpad fragment takes up when fully displayed @@ -385,7 +394,9 @@ public class DialpadFragment extends Fragment @Override public boolean onTouch(View v, MotionEvent event) { if (isDigitsEmpty()) { - hideAndClearDialpad(true); + if (getActivity() != null) { + return ((HostInterface) getActivity()).onDialpadSpacerTouchWithEmptyQuery(); + } return true; } return false; diff --git a/src/com/android/dialer/list/AllContactsFragment.java b/src/com/android/dialer/list/AllContactsFragment.java index d34250b48..0f31ff88f 100644 --- a/src/com/android/dialer/list/AllContactsFragment.java +++ b/src/com/android/dialer/list/AllContactsFragment.java @@ -16,7 +16,14 @@ package com.android.dialer.list; +import static android.Manifest.permission.READ_CONTACTS; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; import android.content.Loader; +import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; import android.provider.ContactsContract.CommonDataKinds.Phone; @@ -34,13 +41,30 @@ import com.android.contacts.common.util.PermissionsUtil; import com.android.contacts.common.util.ViewUtil; import com.android.dialer.R; import com.android.dialer.util.DialerUtils; +import com.android.dialer.util.IntentUtil; +import com.android.dialer.widget.EmptyContentView; +import com.android.dialer.widget.EmptyContentView.OnEmptyViewActionButtonClickedListener; /** * Fragments to show all contacts with phone numbers. */ -public class AllContactsFragment extends ContactEntryListFragment<ContactEntryListAdapter> { +public class AllContactsFragment extends ContactEntryListFragment<ContactEntryListAdapter> + implements OnEmptyViewActionButtonClickedListener { + + private static final int READ_CONTACTS_PERMISSION_REQUEST_CODE = 1; + + private EmptyContentView mEmptyListView; - private View mEmptyListView; + /** + * Listen to broadcast events about permissions in order to be notified if the READ_CONTACTS + * permission is granted via the UI in another fragment. + */ + private BroadcastReceiver mReadContactsPermissionGrantedReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + reloadData(); + } + }; public AllContactsFragment() { setQuickContactEnabled(false); @@ -55,9 +79,10 @@ public class AllContactsFragment extends ContactEntryListFragment<ContactEntryLi public void onViewCreated(View view, android.os.Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - mEmptyListView = view.findViewById(R.id.empty_list_view); - DialerUtils.configureEmptyListView(mEmptyListView, R.drawable.empty_contacts, - R.string.all_contacts_empty, getResources()); + mEmptyListView = (EmptyContentView) view.findViewById(R.id.empty_list_view); + mEmptyListView.setImage(R.drawable.empty_contacts); + mEmptyListView.setDescription(R.string.all_contacts_empty); + mEmptyListView.setActionClickedListener(this); getListView().setEmptyView(mEmptyListView); mEmptyListView.setVisibility(View.GONE); @@ -65,9 +90,29 @@ public class AllContactsFragment extends ContactEntryListFragment<ContactEntryLi } @Override + public void onStart() { + super.onStart(); + PermissionsUtil.registerPermissionReceiver(getActivity(), + mReadContactsPermissionGrantedReceiver, READ_CONTACTS); + } + + @Override + public void onStop() { + PermissionsUtil.unregisterPermissionReceiver(getActivity(), + mReadContactsPermissionGrantedReceiver); + super.onStop(); + } + + @Override protected void startLoading() { - if (PermissionsUtil.hasContactsPermissions(getActivity())) { + if (PermissionsUtil.hasPermission(getActivity(), READ_CONTACTS)) { super.startLoading(); + mEmptyListView.setDescription(R.string.all_contacts_empty); + mEmptyListView.setActionLabel(R.string.all_contacts_empty_add_contact_action); + } else { + mEmptyListView.setDescription(R.string.permission_no_contacts); + mEmptyListView.setActionLabel(R.string.permission_single_turn_on); + mEmptyListView.setVisibility(View.VISIBLE); } } @@ -82,10 +127,6 @@ public class AllContactsFragment extends ContactEntryListFragment<ContactEntryLi @Override protected ContactEntryListAdapter createListAdapter() { - if (!PermissionsUtil.hasContactsPermissions(getActivity())) { - return new EmptyContactsListAdapter(getActivity()); - } - final DefaultContactListAdapter adapter = new DefaultContactListAdapter(getActivity()) { @Override protected void bindView(View itemView, int partition, Cursor cursor, int position) { @@ -102,7 +143,7 @@ public class AllContactsFragment extends ContactEntryListFragment<ContactEntryLi @Override protected View inflateView(LayoutInflater inflater, ViewGroup container) { - return inflater.inflate(R.layout.show_all_contacts_fragment, null); + return inflater.inflate(R.layout.all_contacts_fragment, null); } @Override @@ -118,4 +159,31 @@ public class AllContactsFragment extends ContactEntryListFragment<ContactEntryLi protected void onItemClick(int position, long id) { // Do nothing. Implemented to satisfy ContactEntryListFragment. } + + @Override + public void onEmptyViewActionButtonClicked() { + final Activity activity = getActivity(); + if (activity == null) { + return; + } + + if (!PermissionsUtil.hasPermission(activity, READ_CONTACTS)) { + requestPermissions(new String[] {READ_CONTACTS}, READ_CONTACTS_PERMISSION_REQUEST_CODE); + } else { + // Add new contact + DialerUtils.startActivityWithErrorToast(activity, IntentUtil.getNewContactIntent(), + R.string.add_contact_not_available); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, + int[] grantResults) { + if (requestCode == READ_CONTACTS_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. + reloadData(); + } + } + } } diff --git a/src/com/android/dialer/list/EmptyContactsListAdapter.java b/src/com/android/dialer/list/EmptyContactsListAdapter.java deleted file mode 100644 index 54bd4771f..000000000 --- a/src/com/android/dialer/list/EmptyContactsListAdapter.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2015 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.list; - -import android.content.Context; -import android.content.CursorLoader; - -import com.android.contacts.common.list.ContactEntryListAdapter; - -/** - * Used to display an empty contact list when we don't have the permissions to read contacts. - */ -public class EmptyContactsListAdapter extends ContactEntryListAdapter { - - public EmptyContactsListAdapter(Context context) { - super(context); - } - - @Override - public String getContactDisplayName(int position) { - return null; - } - - @Override - public void configureLoader(CursorLoader loader, long directoryId) { - loader.setUri(null); - } - - @Override - public int getCount() { - return 0; - } -} diff --git a/src/com/android/dialer/list/RegularSearchFragment.java b/src/com/android/dialer/list/RegularSearchFragment.java index 19c7321a1..b7e26d690 100644 --- a/src/com/android/dialer/list/RegularSearchFragment.java +++ b/src/com/android/dialer/list/RegularSearchFragment.java @@ -15,16 +15,29 @@ */ package com.android.dialer.list; +import static android.Manifest.permission.ACCESS_FINE_LOCATION; +import static android.Manifest.permission.READ_CONTACTS; + +import android.app.Activity; +import android.content.pm.PackageManager; import android.view.LayoutInflater; import android.view.ViewGroup; import com.android.contacts.common.list.ContactEntryListAdapter; import com.android.contacts.common.list.PinnedHeaderListView; +import com.android.contacts.common.util.PermissionsUtil; import com.android.contacts.commonbind.analytics.AnalyticsUtil; import com.android.dialerbind.ObjectFactory; + +import com.android.dialer.R; import com.android.dialer.service.CachedNumberLookupService; +import com.android.dialer.widget.EmptyContentView; +import com.android.dialer.widget.EmptyContentView.OnEmptyViewActionButtonClickedListener; -public class RegularSearchFragment extends SearchFragment { +public class RegularSearchFragment extends SearchFragment + implements OnEmptyViewActionButtonClickedListener { + + private static final int READ_CONTACTS_PERMISSION_REQUEST_CODE = 1; private static final int SEARCH_DIRECTORY_RESULT_LIMIT = 5; @@ -68,4 +81,38 @@ public class RegularSearchFragment extends SearchFragment { adapter.getContactInfo(mCachedNumberLookupService, position)); } } + + @Override + protected void setupEmptyView() { + if (mEmptyView != null && getActivity() != null) { + if (!PermissionsUtil.hasPermission(getActivity(), READ_CONTACTS)) { + mEmptyView.setImage(R.drawable.empty_contacts); + mEmptyView.setActionLabel(R.string.permission_single_turn_on); + mEmptyView.setDescription(R.string.permission_no_search); + mEmptyView.setActionClickedListener(this); + } else { + mEmptyView.setImage(EmptyContentView.NO_IMAGE); + mEmptyView.setActionLabel(EmptyContentView.NO_LABEL); + mEmptyView.setDescription(EmptyContentView.NO_LABEL); + } + } + } + + @Override + public void onEmptyViewActionButtonClicked() { + final Activity activity = getActivity(); + if (activity == null) { + return; + } + + requestPermissions(new String[] {READ_CONTACTS}, READ_CONTACTS_PERMISSION_REQUEST_CODE); + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, + int[] grantResults) { + if (requestCode == READ_CONTACTS_PERMISSION_REQUEST_CODE) { + setupEmptyView(); + } + } } diff --git a/src/com/android/dialer/list/SearchFragment.java b/src/com/android/dialer/list/SearchFragment.java index 77ab29198..315cfb914 100644 --- a/src/com/android/dialer/list/SearchFragment.java +++ b/src/com/android/dialer/list/SearchFragment.java @@ -15,6 +15,8 @@ */ package com.android.dialer.list; +import static android.Manifest.permission.READ_CONTACTS; + import android.animation.Animator; import android.animation.AnimatorInflater; import android.animation.AnimatorListenerAdapter; @@ -50,6 +52,7 @@ import com.android.dialer.dialpad.DialpadFragment.ErrorDialogFragment; import com.android.dialer.R; import com.android.dialer.util.DialerUtils; import com.android.dialer.util.IntentUtil; +import com.android.dialer.widget.EmptyContentView; import com.android.phone.common.animation.AnimUtils; public class SearchFragment extends PhoneNumberPickerFragment { @@ -79,6 +82,8 @@ public class SearchFragment extends PhoneNumberPickerFragment { private HostInterface mActivity; + protected EmptyContentView mEmptyView; + public interface HostInterface { public boolean isActionBarShowing(); public boolean isDialpadShown(); @@ -125,6 +130,13 @@ public class SearchFragment extends PhoneNumberPickerFragment { final ListView listView = getListView(); + if (mEmptyView == null) { + mEmptyView = new EmptyContentView(getActivity()); + ((ViewGroup) getListView().getParent()).addView(mEmptyView); + getListView().setEmptyView(mEmptyView); + setupEmptyView(); + } + listView.setBackgroundColor(res.getColor(R.color.background_dialer_results)); listView.setClipToPadding(false); setVisibleScrollbarEnabled(false); @@ -341,7 +353,7 @@ public class SearchFragment extends PhoneNumberPickerFragment { @Override protected void startLoading() { - if (PermissionsUtil.hasContactsPermissions(getActivity())) { + if (PermissionsUtil.hasPermission(getActivity(), READ_CONTACTS)) { super.startLoading(); } else if (TextUtils.isEmpty(getQueryString())) { // Clear out any existing call shortcuts. @@ -354,6 +366,8 @@ public class SearchFragment extends PhoneNumberPickerFragment { // list. getAdapter().notifyDataSetChanged(); } + + setupEmptyView(); } public void setOnTouchListener(View.OnTouchListener onTouchListener) { @@ -371,4 +385,6 @@ public class SearchFragment extends PhoneNumberPickerFragment { } return parent; } + + protected void setupEmptyView() {} } diff --git a/src/com/android/dialer/list/SmartDialSearchFragment.java b/src/com/android/dialer/list/SmartDialSearchFragment.java index 082bc4360..72d3abf68 100644 --- a/src/com/android/dialer/list/SmartDialSearchFragment.java +++ b/src/com/android/dialer/list/SmartDialSearchFragment.java @@ -15,21 +15,33 @@ */ package com.android.dialer.list; +import static android.Manifest.permission.CALL_PHONE; + +import android.app.Activity; import android.content.Loader; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.util.Log; +import android.view.View; import com.android.contacts.common.list.ContactEntryListAdapter; +import com.android.contacts.common.util.PermissionsUtil; import com.android.dialer.dialpad.SmartDialCursorLoader; +import com.android.dialer.R; +import com.android.dialer.widget.EmptyContentView; + +import java.util.ArrayList; /** * Implements a fragment to load and display SmartDial search results. */ -public class SmartDialSearchFragment extends SearchFragment { +public class SmartDialSearchFragment extends SearchFragment + implements EmptyContentView.OnEmptyViewActionButtonClickedListener { private static final String TAG = SmartDialSearchFragment.class.getSimpleName(); + private static final int CALL_PHONE_PERMISSION_REQUEST_CODE = 1; + /** * Creates a SmartDialListAdapter to display and operate on search results. */ @@ -69,4 +81,42 @@ public class SmartDialSearchFragment extends SearchFragment { final SmartDialNumberListAdapter adapter = (SmartDialNumberListAdapter) getAdapter(); return adapter.getDataUri(position); } + + @Override + protected void setupEmptyView() { + if (mEmptyView != null && getActivity() != null) { + if (!PermissionsUtil.hasPermission(getActivity(), CALL_PHONE)) { + mEmptyView.setImage(R.drawable.empty_contacts); + mEmptyView.setActionLabel(R.string.permission_single_turn_on); + mEmptyView.setDescription(R.string.permission_place_call); + mEmptyView.setActionClickedListener(this); + } else { + mEmptyView.setImage(EmptyContentView.NO_IMAGE); + mEmptyView.setActionLabel(EmptyContentView.NO_LABEL); + mEmptyView.setDescription(EmptyContentView.NO_LABEL); + } + } + } + + @Override + public void onEmptyViewActionButtonClicked() { + final Activity activity = getActivity(); + if (activity == null) { + return; + } + + requestPermissions(new String[] {CALL_PHONE}, CALL_PHONE_PERMISSION_REQUEST_CODE); + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, + int[] grantResults) { + if (requestCode == CALL_PHONE_PERMISSION_REQUEST_CODE) { + setupEmptyView(); + } + } + + public boolean isShowingPermissionRequest() { + return mEmptyView != null && mEmptyView.isShowingContent(); + } } diff --git a/src/com/android/dialer/list/SpeedDialFragment.java b/src/com/android/dialer/list/SpeedDialFragment.java index bf9575858..324caefb6 100644 --- a/src/com/android/dialer/list/SpeedDialFragment.java +++ b/src/com/android/dialer/list/SpeedDialFragment.java @@ -15,6 +15,8 @@ */ package com.android.dialer.list; +import static android.Manifest.permission.READ_CONTACTS; + import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; @@ -23,6 +25,7 @@ import android.app.Fragment; import android.app.LoaderManager; import android.content.CursorLoader; import android.content.Loader; +import android.content.pm.PackageManager; import android.database.Cursor; import android.graphics.Rect; import android.net.Uri; @@ -50,6 +53,7 @@ import com.android.contacts.common.list.OnPhoneNumberPickerActionListener; import com.android.contacts.common.util.PermissionsUtil; import com.android.dialer.R; import com.android.dialer.util.DialerUtils; +import com.android.dialer.widget.EmptyContentView; import java.util.ArrayList; import java.util.HashMap; @@ -58,7 +62,10 @@ import java.util.HashMap; * This fragment displays the user's favorite/frequent contacts in a grid. */ public class SpeedDialFragment extends Fragment implements OnItemClickListener, - PhoneFavoritesTileAdapter.OnDataSetChangedForAnimationListener { + PhoneFavoritesTileAdapter.OnDataSetChangedForAnimationListener, + EmptyContentView.OnEmptyViewActionButtonClickedListener { + + private static final int READ_CONTACTS_PERMISSION_REQUEST_CODE = 1; /** * By default, the animation code assumes that all items in a list view are of the same height @@ -81,6 +88,7 @@ public class SpeedDialFragment extends Fragment implements OnItemClickListener, public interface HostInterface { public void setDragDropController(DragDropController controller); + public void showAllContactsTab(); } private class ContactTileLoaderListener implements LoaderManager.LoaderCallbacks<Cursor> { @@ -157,7 +165,7 @@ public class SpeedDialFragment extends Fragment implements OnItemClickListener, /** * Layout used when there are no favorites. */ - private View mEmptyView; + private EmptyContentView mEmptyView; private final ContactTileView.Listener mContactTileAdapterListener = new ContactTileAdapterListener(); @@ -197,9 +205,16 @@ public class SpeedDialFragment extends Fragment implements OnItemClickListener, if (getLoaderManager().getLoader(LOADER_ID_CONTACT_TILE) == null) { getLoaderManager().initLoader(LOADER_ID_CONTACT_TILE, null, mContactTileLoaderListener); + } else { getLoaderManager().getLoader(LOADER_ID_CONTACT_TILE).forceLoad(); } + + mEmptyView.setDescription(R.string.speed_dial_empty); + mEmptyView.setActionLabel(R.string.speed_dial_empty_add_favorite_action); + } else { + mEmptyView.setDescription(R.string.permission_no_speeddial); + mEmptyView.setActionLabel(R.string.permission_single_turn_on); } Trace.endSection(); } @@ -221,9 +236,9 @@ public class SpeedDialFragment extends Fragment implements OnItemClickListener, (ImageView) getActivity().findViewById(R.id.contact_tile_drag_shadow_overlay); mListView.setDragShadowOverlay(dragShadowOverlay); - mEmptyView = mParentView.findViewById(R.id.empty_list_view); - DialerUtils.configureEmptyListView( - mEmptyView, R.drawable.empty_speed_dial, R.string.speed_dial_empty, getResources()); + mEmptyView = (EmptyContentView) mParentView.findViewById(R.id.empty_list_view); + mEmptyView.setImage(R.drawable.empty_speed_dial); + mEmptyView.setActionClickedListener(this); mContactTileFrame = mParentView.findViewById(R.id.contact_tile_frame); @@ -449,4 +464,29 @@ public class SpeedDialFragment extends Fragment implements OnItemClickListener, public AbsListView getListView() { return mListView; } + + @Override + public void onEmptyViewActionButtonClicked() { + final Activity activity = getActivity(); + if (activity == null) { + return; + } + + if (!PermissionsUtil.hasPermission(activity, READ_CONTACTS)) { + requestPermissions(new String[] {READ_CONTACTS}, READ_CONTACTS_PERMISSION_REQUEST_CODE); + } else { + // Switch tabs + ((HostInterface) activity).showAllContactsTab(); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, + int[] grantResults) { + if (requestCode == READ_CONTACTS_PERMISSION_REQUEST_CODE) { + if (grantResults.length == 1 && PackageManager.PERMISSION_GRANTED == grantResults[0]) { + PermissionsUtil.notifyPermissionGranted(getActivity(), READ_CONTACTS); + } + } + } } diff --git a/src/com/android/dialer/util/DialerUtils.java b/src/com/android/dialer/util/DialerUtils.java index a44c2eec6..e25ada59d 100644 --- a/src/com/android/dialer/util/DialerUtils.java +++ b/src/com/android/dialer/util/DialerUtils.java @@ -40,6 +40,7 @@ import android.widget.Toast; import com.android.contacts.common.ContactsUtils; import com.android.contacts.common.interactions.TouchPointManager; import com.android.dialer.R; +import com.android.dialer.widget.EmptyContentView; import com.android.incallui.CallCardFragment; import com.android.incallui.Log; @@ -116,27 +117,6 @@ public class DialerUtils { } /** - * Sets the image asset and text for an empty list view (see empty_list_view.xml). - * - * @param emptyListView The empty list view. - * @param imageResId The resource id for the drawable to set as the image. - * @param strResId The resource id for the string to set as the message. - * @param res The resources to obtain the image and string from. - */ - public static void configureEmptyListView( - View emptyListView, int imageResId, int strResId, Resources res) { - ImageView emptyListViewImage = - (ImageView) emptyListView.findViewById(R.id.emptyListViewImage); - - emptyListViewImage.setImageDrawable(res.getDrawable(imageResId)); - emptyListViewImage.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); - - TextView emptyListViewMessage = - (TextView) emptyListView.findViewById(R.id.emptyListViewMessage); - emptyListViewMessage.setText(res.getString(strResId)); - } - - /** * Closes an {@link AutoCloseable}, silently ignoring any checked exceptions. Does nothing if * null. * diff --git a/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java b/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java index 2017bc578..213bba164 100644 --- a/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java +++ b/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java @@ -183,7 +183,9 @@ public class VoicemailPlaybackLayout extends LinearLayout if (mPresenter == null) { return; } + mPresenter.pausePlayback(); CallLogAsyncTaskUtil.deleteVoicemail(mContext, mVoicemailUri, null); + mPresenter.onVoicemailDeleted(); } }; diff --git a/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java b/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java index fb9e3edfb..7270af787 100644 --- a/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java +++ b/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java @@ -92,6 +92,10 @@ public class VoicemailPlaybackPresenter void setPresenter(VoicemailPlaybackPresenter presenter, Uri voicemailUri); } + public interface OnVoicemailDeletedListener { + void onVoicemailDeleted(Uri uri); + } + /** The enumeration of {@link AsyncTask} objects we use in this class. */ public enum Tasks { CHECK_FOR_CONTENT, @@ -155,6 +159,8 @@ public class VoicemailPlaybackPresenter private PowerManager.WakeLock mProximityWakeLock; private AudioManager mAudioManager; + private OnVoicemailDeletedListener mOnVoicemailDeletedListener; + /** * Obtain singleton instance of this class. Use a single instance to provide a consistent * listener to the AudioManager when requesting and abandoning audio focus. @@ -634,7 +640,8 @@ public class VoicemailPlaybackPresenter mMediaPlayer.pause(); } - mPosition = mMediaPlayer.getCurrentPosition(); + mPosition = mMediaPlayer == null ? 0 : mMediaPlayer.getCurrentPosition(); + Log.d(TAG, "Paused playback at " + mPosition + "."); if (mView != null) { @@ -653,7 +660,9 @@ public class VoicemailPlaybackPresenter * playing to know whether to resume playback once the user selects a new position. */ public void pausePlaybackForSeeking() { - mShouldResumePlaybackAfterSeeking = mMediaPlayer.isPlaying(); + if (mMediaPlayer != null) { + mShouldResumePlaybackAfterSeeking = mMediaPlayer.isPlaying(); + } pausePlayback(); } @@ -712,10 +721,21 @@ public class VoicemailPlaybackPresenter return mAudioManager.isSpeakerphoneOn(); } + public void setOnVoicemailDeletedListener(OnVoicemailDeletedListener listener) { + mOnVoicemailDeletedListener = listener; + } + public int getMediaPlayerPosition() { return mIsPrepared && mMediaPlayer != null ? mMediaPlayer.getCurrentPosition() : 0; } + /* package */ void onVoicemailDeleted() { + // Trampoline the event notification to the interested listener + if (mOnVoicemailDeletedListener != null) { + mOnVoicemailDeletedListener.onVoicemailDeleted(mVoicemailUri); + } + } + private static synchronized ScheduledExecutorService getScheduledExecutorServiceInstance() { if (mScheduledExecutorService == null) { mScheduledExecutorService = Executors.newScheduledThreadPool(NUMBER_OF_THREADS_IN_POOL); diff --git a/src/com/android/dialer/widget/EmptyContentView.java b/src/com/android/dialer/widget/EmptyContentView.java new file mode 100644 index 000000000..f248967de --- /dev/null +++ b/src/com/android/dialer/widget/EmptyContentView.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2015 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.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.dialer.R; + +public class EmptyContentView extends LinearLayout implements View.OnClickListener { + + public static final int NO_LABEL = 0; + public static final int NO_IMAGE = 0; + + private ImageView mImageView; + private TextView mDescriptionView; + private TextView mActionView; + private OnEmptyViewActionButtonClickedListener mOnActionButtonClickedListener; + + public interface OnEmptyViewActionButtonClickedListener { + public void onEmptyViewActionButtonClicked(); + } + + public EmptyContentView(Context context) { + this(context, null); + } + + public EmptyContentView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public EmptyContentView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public EmptyContentView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + setOrientation(LinearLayout.VERTICAL); + + final LayoutInflater inflater = (LayoutInflater) getContext().getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + inflater.inflate(R.layout.empty_content_view, this); + // Don't let touches fall through the empty view. + setClickable(true); + mImageView = (ImageView) findViewById(R.id.emptyListViewImage); + mDescriptionView = (TextView) findViewById(R.id.emptyListViewMessage); + mActionView = (TextView) findViewById(R.id.emptyListViewAction); + mActionView.setOnClickListener(this); + } + + public void setDescription(int resourceId) { + if (resourceId == NO_LABEL) { + mDescriptionView.setText(null); + mDescriptionView.setVisibility(View.GONE); + } else { + mDescriptionView.setText(resourceId); + mDescriptionView.setVisibility(View.VISIBLE); + } + } + + public void setImage(int resourceId) { + mImageView.setImageResource(resourceId); + if (resourceId == NO_LABEL) { + mImageView.setVisibility(View.GONE); + } else { + mImageView.setVisibility(View.VISIBLE); + } + } + + public void setActionLabel(int resourceId) { + if (resourceId == NO_LABEL) { + mActionView.setText(null); + mActionView.setVisibility(View.GONE); + } else { + mActionView.setText(resourceId); + mActionView.setVisibility(View.VISIBLE); + } + } + + public boolean isShowingContent() { + return mImageView.getVisibility() == View.VISIBLE + || mDescriptionView.getVisibility() == View.VISIBLE + || mActionView.getVisibility() == View.VISIBLE; + } + + public void setActionClickedListener(OnEmptyViewActionButtonClickedListener listener) { + mOnActionButtonClickedListener = listener; + } + + @Override + public void onClick(View v) { + if (mOnActionButtonClickedListener != null) { + mOnActionButtonClickedListener.onEmptyViewActionButtonClicked(); + } + } +} |