diff options
author | Eric Erfanian <erfanian@google.com> | 2017-03-15 14:41:07 -0700 |
---|---|---|
committer | Eric Erfanian <erfanian@google.com> | 2017-03-15 16:24:23 -0700 |
commit | d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9 (patch) | |
tree | b54abbb51fb7d66e7755a1fbb5db023ff601090b /java/com/android/dialer/app | |
parent | 30436e7e6d3f2c8755a91b2b6222b74d465a9e87 (diff) | |
download | android_packages_apps_Dialer-d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9.tar.gz android_packages_apps_Dialer-d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9.tar.bz2 android_packages_apps_Dialer-d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9.zip |
Update Dialer source from latest green build.
* Refactor voicemail component
* Add new enriched calling components
Test: treehugger, manual aosp testing
Change-Id: I521a0f86327d4b42e14d93927c7d613044ed5942
Diffstat (limited to 'java/com/android/dialer/app')
68 files changed, 1479 insertions, 2671 deletions
diff --git a/java/com/android/dialer/app/AndroidManifest.xml b/java/com/android/dialer/app/AndroidManifest.xml index 80f294acc..5ce13dbd7 100644 --- a/java/com/android/dialer/app/AndroidManifest.xml +++ b/java/com/android/dialer/app/AndroidManifest.xml @@ -57,11 +57,7 @@ android:minSdkVersion="23" android:targetSdkVersion="25"/> - <application - android:backupAgent='com.android.dialer.backup.DialerBackupAgent' - android:fullBackupOnly="true" - android:restoreAnyVersion="true" - android:name="com.android.dialer.app.DialerApplication"> + <application android:theme="@style/Theme.AppCompat"> <activity android:exported="false" @@ -75,6 +71,12 @@ </intent-filter> </activity> + <activity + android:label="@string/call_log_activity_title" + android:name="com.android.dialer.app.calllog.CallLogActivity" + android:theme="@style/DialtactsThemeWithoutActionBarOverlay"> + </activity> + <receiver android:name="com.android.dialer.app.calllog.CallLogReceiver"> <intent-filter> <action android:name="android.intent.action.NEW_VOICEMAIL"/> diff --git a/java/com/android/dialer/app/CallDetailActivity.java b/java/com/android/dialer/app/CallDetailActivity.java deleted file mode 100644 index cda2b2e2c..000000000 --- a/java/com/android/dialer/app/CallDetailActivity.java +++ /dev/null @@ -1,480 +0,0 @@ -/* - * Copyright (C) 2009 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.app; - -import android.content.ContentUris; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.support.v7.app.AppCompatActivity; -import android.text.BidiFormatter; -import android.text.TextDirectionHeuristics; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.widget.ListView; -import android.widget.QuickContactBadge; -import android.widget.TextView; -import android.widget.Toast; -import com.android.contacts.common.ClipboardUtils; -import com.android.contacts.common.ContactPhotoManager; -import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest; -import com.android.contacts.common.GeoUtil; -import com.android.contacts.common.preference.ContactsPreferences; -import com.android.contacts.common.util.UriUtils; -import com.android.dialer.app.calllog.CallDetailHistoryAdapter; -import com.android.dialer.app.calllog.CallLogAsyncTaskUtil; -import com.android.dialer.app.calllog.CallLogAsyncTaskUtil.CallLogAsyncTaskListener; -import com.android.dialer.app.calllog.CallTypeHelper; -import com.android.dialer.app.calllog.PhoneAccountUtils; -import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler; -import com.android.dialer.callintent.CallIntentBuilder; -import com.android.dialer.callintent.nano.CallInitiationType; -import com.android.dialer.common.Assert; -import com.android.dialer.common.AsyncTaskExecutor; -import com.android.dialer.common.AsyncTaskExecutors; -import com.android.dialer.compat.CompatUtils; -import com.android.dialer.logging.Logger; -import com.android.dialer.logging.nano.DialerImpression; -import com.android.dialer.logging.nano.ScreenEvent; -import com.android.dialer.phonenumbercache.ContactInfoHelper; -import com.android.dialer.phonenumberutil.PhoneNumberHelper; -import com.android.dialer.proguard.UsedByReflection; -import com.android.dialer.spam.Spam; -import com.android.dialer.telecom.TelecomUtil; -import com.android.dialer.util.CallUtil; -import com.android.dialer.util.DialerUtils; -import com.android.dialer.util.TouchPointManager; - -/** - * Displays the details of a specific call log entry. - * - * <p>This activity can be either started with the URI of a single call log entry, or with the - * {@link #EXTRA_CALL_LOG_IDS} extra to specify a group of call log entries. - */ -@UsedByReflection(value = "AndroidManifest-app.xml") -public class CallDetailActivity extends AppCompatActivity - implements MenuItem.OnMenuItemClickListener, View.OnClickListener { - - /** A long array extra containing ids of call log entries to display. */ - public static final String EXTRA_CALL_LOG_IDS = "EXTRA_CALL_LOG_IDS"; - /** If we are started with a voicemail, we'll find the uri to play with this extra. */ - public static final String EXTRA_VOICEMAIL_URI = "EXTRA_VOICEMAIL_URI"; - /** If the activity was triggered from a notification. */ - public static final String EXTRA_FROM_NOTIFICATION = "EXTRA_FROM_NOTIFICATION"; - - public static final String BLOCKED_OR_SPAM_QUERY_IDENTIFIER = "blockedOrSpamIdentifier"; - - private final AsyncTaskExecutor executor = AsyncTaskExecutors.createAsyncTaskExecutor(); - protected String mNumber; - private Context mContext; - private ContactInfoHelper mContactInfoHelper; - private ContactsPreferences mContactsPreferences; - private CallTypeHelper mCallTypeHelper; - private ContactPhotoManager mContactPhotoManager; - private BidiFormatter mBidiFormatter = BidiFormatter.getInstance(); - private LayoutInflater mInflater; - private Resources mResources; - private PhoneCallDetails mDetails; - private Uri mVoicemailUri; - private String mPostDialDigits = ""; - private ListView mHistoryList; - private QuickContactBadge mQuickContactBadge; - private TextView mCallerName; - private TextView mCallerNumber; - private TextView mAccountLabel; - private View mCallButton; - private View mEditBeforeCallActionItem; - private View mReportActionItem; - private View mCopyNumberActionItem; - private FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler; - private CallLogAsyncTaskListener mCallLogAsyncTaskListener = - new CallLogAsyncTaskListener() { - @Override - public void onDeleteCall() { - finish(); - } - - @Override - public void onDeleteVoicemail() { - finish(); - } - - @Override - public void onGetCallDetails(final PhoneCallDetails[] details) { - if (details == null) { - // Somewhere went wrong: we're going to bail out and show error to users. - Toast.makeText(mContext, R.string.toast_call_detail_error, Toast.LENGTH_SHORT).show(); - finish(); - return; - } - - // All calls are from the same number and same contact, so pick the first detail. - mDetails = details[0]; - mNumber = TextUtils.isEmpty(mDetails.number) ? null : mDetails.number.toString(); - - if (mNumber == null) { - updateDataAndRender(details); - return; - } - - executor.submit( - BLOCKED_OR_SPAM_QUERY_IDENTIFIER, - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... params) { - mDetails.isBlocked = - mFilteredNumberAsyncQueryHandler.getBlockedIdSynchronousForCalllogOnly( - mNumber, mDetails.countryIso) - != null; - if (Spam.get(mContext).isSpamEnabled()) { - mDetails.isSpam = - hasIncomingCalls(details) - && Spam.get(mContext) - .checkSpamStatusSynchronous(mNumber, mDetails.countryIso); - } - return null; - } - - @Override - protected void onPostExecute(Void result) { - updateDataAndRender(details); - } - }); - } - - private void updateDataAndRender(PhoneCallDetails[] details) { - mPostDialDigits = - TextUtils.isEmpty(mDetails.postDialDigits) ? "" : mDetails.postDialDigits; - - final CharSequence callLocationOrType = getNumberTypeOrLocation(mDetails); - - final CharSequence displayNumber; - if (!TextUtils.isEmpty(mDetails.postDialDigits)) { - displayNumber = mDetails.number + mDetails.postDialDigits; - } else { - displayNumber = mDetails.displayNumber; - } - - final String displayNumberStr = - mBidiFormatter.unicodeWrap(displayNumber.toString(), TextDirectionHeuristics.LTR); - - mDetails.nameDisplayOrder = mContactsPreferences.getDisplayOrder(); - - if (!TextUtils.isEmpty(mDetails.getPreferredName())) { - mCallerName.setText(mDetails.getPreferredName()); - mCallerNumber.setText(callLocationOrType + " " + displayNumberStr); - } else { - mCallerName.setText(displayNumberStr); - if (!TextUtils.isEmpty(callLocationOrType)) { - mCallerNumber.setText(callLocationOrType); - mCallerNumber.setVisibility(View.VISIBLE); - } else { - mCallerNumber.setVisibility(View.GONE); - } - } - - CharSequence accountLabel = - PhoneAccountUtils.getAccountLabel(mContext, mDetails.accountHandle); - CharSequence accountContentDescription = - PhoneCallDetails.createAccountLabelDescription( - mResources, mDetails.viaNumber, accountLabel); - if (!TextUtils.isEmpty(mDetails.viaNumber)) { - if (!TextUtils.isEmpty(accountLabel)) { - accountLabel = - mResources.getString( - R.string.call_log_via_number_phone_account, accountLabel, mDetails.viaNumber); - } else { - accountLabel = mResources.getString(R.string.call_log_via_number, mDetails.viaNumber); - } - } - if (!TextUtils.isEmpty(accountLabel)) { - mAccountLabel.setText(accountLabel); - mAccountLabel.setContentDescription(accountContentDescription); - mAccountLabel.setVisibility(View.VISIBLE); - } else { - mAccountLabel.setVisibility(View.GONE); - } - - final boolean canPlaceCallsTo = - PhoneNumberHelper.canPlaceCallsTo(mNumber, mDetails.numberPresentation); - mCallButton.setVisibility(canPlaceCallsTo ? View.VISIBLE : View.GONE); - mCopyNumberActionItem.setVisibility(canPlaceCallsTo ? View.VISIBLE : View.GONE); - - final boolean isSipNumber = PhoneNumberHelper.isSipNumber(mNumber); - final boolean isVoicemailNumber = - PhoneNumberHelper.isVoicemailNumber(mContext, mDetails.accountHandle, mNumber); - final boolean showEditNumberBeforeCallAction = - canPlaceCallsTo && !isSipNumber && !isVoicemailNumber; - mEditBeforeCallActionItem.setVisibility( - showEditNumberBeforeCallAction ? View.VISIBLE : View.GONE); - - final boolean showReportAction = - mContactInfoHelper.canReportAsInvalid(mDetails.sourceType, mDetails.objectId); - mReportActionItem.setVisibility(showReportAction ? View.VISIBLE : View.GONE); - - invalidateOptionsMenu(); - - mHistoryList.setAdapter( - new CallDetailHistoryAdapter(mContext, mInflater, mCallTypeHelper, details)); - - updateContactPhoto(mDetails.isSpam); - - findViewById(R.id.call_detail).setVisibility(View.VISIBLE); - } - - /** - * Determines the location geocode text for a call, or the phone number type (if available). - * - * @param details The call details. - * @return The phone number type or location. - */ - private CharSequence getNumberTypeOrLocation(PhoneCallDetails details) { - if (details.isSpam) { - return mResources.getString(R.string.spam_number_call_log_label); - } else if (details.isBlocked) { - return mResources.getString(R.string.blocked_number_call_log_label); - } else if (!TextUtils.isEmpty(details.namePrimary)) { - return Phone.getTypeLabel(mResources, details.numberType, details.numberLabel); - } else { - return details.geocode; - } - } - }; - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - - mContext = this; - mResources = getResources(); - mContactInfoHelper = new ContactInfoHelper(this, GeoUtil.getCurrentCountryIso(this)); - mContactsPreferences = new ContactsPreferences(mContext); - mCallTypeHelper = new CallTypeHelper(getResources()); - mFilteredNumberAsyncQueryHandler = new FilteredNumberAsyncQueryHandler(mContext); - - mVoicemailUri = getIntent().getParcelableExtra(EXTRA_VOICEMAIL_URI); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - setContentView(R.layout.call_detail); - mInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); - - mHistoryList = (ListView) findViewById(R.id.history); - mHistoryList.addHeaderView(mInflater.inflate(R.layout.call_detail_header, null)); - mHistoryList.addFooterView(mInflater.inflate(R.layout.call_detail_footer, null), null, false); - - mQuickContactBadge = (QuickContactBadge) findViewById(R.id.quick_contact_photo); - mQuickContactBadge.setOverlay(null); - if (CompatUtils.hasPrioritizedMimeType()) { - mQuickContactBadge.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE); - } - mCallerName = (TextView) findViewById(R.id.caller_name); - mCallerNumber = (TextView) findViewById(R.id.caller_number); - mAccountLabel = (TextView) findViewById(R.id.phone_account_label); - mContactPhotoManager = ContactPhotoManager.getInstance(this); - - mCallButton = findViewById(R.id.call_back_button); - mCallButton.setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View view) { - if (TextUtils.isEmpty(mNumber)) { - return; - } - DialerUtils.startActivityWithErrorToast( - CallDetailActivity.this, - new CallIntentBuilder(getDialableNumber(), CallInitiationType.Type.CALL_DETAILS) - .build()); - } - }); - - mEditBeforeCallActionItem = findViewById(R.id.call_detail_action_edit_before_call); - mEditBeforeCallActionItem.setOnClickListener(this); - mReportActionItem = findViewById(R.id.call_detail_action_report); - mReportActionItem.setOnClickListener(this); - - mCopyNumberActionItem = findViewById(R.id.call_detail_action_copy); - mCopyNumberActionItem.setOnClickListener(this); - - if (getIntent().getBooleanExtra(EXTRA_FROM_NOTIFICATION, false)) { - closeSystemDialogs(); - } - - Logger.get(this).logScreenView(ScreenEvent.Type.CALL_DETAILS, this); - } - - @Override - public void onResume() { - super.onResume(); - mContactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY); - getCallDetails(); - } - - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - TouchPointManager.getInstance().setPoint((int) ev.getRawX(), (int) ev.getRawY()); - } - return super.dispatchTouchEvent(ev); - } - - public void getCallDetails() { - CallLogAsyncTaskUtil.getCallDetails(this, mCallLogAsyncTaskListener, getCallLogEntryUris()); - } - - /** - * Returns the list of URIs to show. - * - * <p>There are two ways the URIs can be provided to the activity: as the data on the intent, or - * as a list of ids in the call log added as an extra on the URI. - * - * <p>If both are available, the data on the intent takes precedence. - */ - private Uri[] getCallLogEntryUris() { - final Uri uri = getIntent().getData(); - if (uri != null) { - // If there is a data on the intent, it takes precedence over the extra. - return new Uri[] {uri}; - } - final long[] ids = getIntent().getLongArrayExtra(EXTRA_CALL_LOG_IDS); - final int numIds = ids == null ? 0 : ids.length; - final Uri[] uris = new Uri[numIds]; - for (int index = 0; index < numIds; ++index) { - uris[index] = - ContentUris.withAppendedId( - TelecomUtil.getCallLogUri(CallDetailActivity.this), ids[index]); - } - return uris; - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - final MenuItem deleteMenuItem = - menu.add( - Menu.NONE, R.id.call_detail_delete_menu_item, Menu.NONE, R.string.call_details_delete); - deleteMenuItem.setIcon(R.drawable.ic_delete_24dp); - deleteMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); - deleteMenuItem.setOnMenuItemClickListener(this); - - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onMenuItemClick(MenuItem item) { - if (item.getItemId() == R.id.call_detail_delete_menu_item) { - Logger.get(mContext).logImpression(DialerImpression.Type.USER_DELETED_CALL_LOG_ITEM); - if (hasVoicemail()) { - CallLogAsyncTaskUtil.deleteVoicemail(this, mVoicemailUri, mCallLogAsyncTaskListener); - } else { - final StringBuilder callIds = new StringBuilder(); - for (Uri callUri : getCallLogEntryUris()) { - if (callIds.length() != 0) { - callIds.append(","); - } - callIds.append(ContentUris.parseId(callUri)); - } - CallLogAsyncTaskUtil.deleteCalls(this, callIds.toString(), mCallLogAsyncTaskListener); - } - } - return true; - } - - @Override - public void onClick(View view) { - int resId = view.getId(); - if (resId == R.id.call_detail_action_copy) { - ClipboardUtils.copyText(mContext, null, mNumber, true); - } else if (resId == R.id.call_detail_action_edit_before_call) { - Intent dialIntent = new Intent(Intent.ACTION_DIAL, CallUtil.getCallUri(getDialableNumber())); - DialerUtils.startActivityWithErrorToast(mContext, dialIntent); - } else { - Assert.fail("Unexpected onClick event from " + view); - } - } - - // Loads and displays the contact photo. - private void updateContactPhoto(boolean isSpam) { - if (mDetails == null) { - return; - } - - mQuickContactBadge.assignContactUri(mDetails.contactUri); - final String displayName = - TextUtils.isEmpty(mDetails.namePrimary) - ? mDetails.displayNumber - : mDetails.namePrimary.toString(); - mQuickContactBadge.setContentDescription( - mResources.getString(R.string.description_contact_details, displayName)); - - final boolean isVoicemailNumber = - PhoneNumberHelper.isVoicemailNumber(mContext, mDetails.accountHandle, mNumber); - if (isSpam) { - mQuickContactBadge.setImageDrawable(mContext.getDrawable(R.drawable.blocked_contact)); - return; - } - - final boolean isBusiness = mContactInfoHelper.isBusiness(mDetails.sourceType); - int contactType = ContactPhotoManager.TYPE_DEFAULT; - if (isVoicemailNumber) { - contactType = ContactPhotoManager.TYPE_VOICEMAIL; - } else if (isBusiness) { - contactType = ContactPhotoManager.TYPE_BUSINESS; - } - - final String lookupKey = - mDetails.contactUri == null ? null : UriUtils.getLookupKeyFromUri(mDetails.contactUri); - - final DefaultImageRequest request = - new DefaultImageRequest(displayName, lookupKey, contactType, true /* isCircular */); - - mContactPhotoManager.loadDirectoryPhoto( - mQuickContactBadge, - mDetails.photoUri, - false /* darkTheme */, - true /* isCircular */, - request); - } - - private void closeSystemDialogs() { - sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); - } - - private String getDialableNumber() { - return mNumber + mPostDialDigits; - } - - public boolean hasVoicemail() { - return mVoicemailUri != null; - } - - private static boolean hasIncomingCalls(PhoneCallDetails[] details) { - for (int i = 0; i < details.length; i++) { - if (details[i].hasIncomingCalls()) { - return true; - } - } - return false; - } -} diff --git a/java/com/android/dialer/app/DialerApplication.java b/java/com/android/dialer/app/DialerApplication.java deleted file mode 100644 index 3b979212b..000000000 --- a/java/com/android/dialer/app/DialerApplication.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2013 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.app; - -import android.app.Application; -import android.os.Trace; -import android.preference.PreferenceManager; -import com.android.dialer.blocking.BlockedNumbersAutoMigrator; -import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler; -import com.android.dialer.enrichedcall.EnrichedCallManager; -import com.android.dialer.inject.ApplicationModule; -import com.android.dialer.inject.DaggerDialerAppComponent; -import com.android.dialer.inject.DialerAppComponent; - -public class DialerApplication extends Application implements EnrichedCallManager.Factory { - - private static final String TAG = "DialerApplication"; - - private volatile DialerAppComponent component; - - @Override - public void onCreate() { - Trace.beginSection(TAG + " onCreate"); - super.onCreate(); - new BlockedNumbersAutoMigrator( - this, - PreferenceManager.getDefaultSharedPreferences(this), - new FilteredNumberAsyncQueryHandler(this)) - .autoMigrate(); - Trace.endSection(); - } - - @Override - public EnrichedCallManager getEnrichedCallManager() { - return component().enrichedCallManager(); - } - - protected DialerAppComponent buildApplicationComponent() { - return DaggerDialerAppComponent.builder() - .applicationModule(new ApplicationModule(this)) - .build(); - } - - /** - * Returns the application component. - * - * <p>A single Component is created per application instance. Note that it won't be instantiated - * until it's first requested, but guarantees that only one will ever be created. - */ - private final DialerAppComponent component() { - // Double-check idiom for lazy initialization - DialerAppComponent result = component; - if (result == null) { - synchronized (this) { - result = component; - if (result == null) { - component = result = buildApplicationComponent(); - } - } - } - return result; - } -} diff --git a/java/com/android/dialer/app/DialtactsActivity.java b/java/com/android/dialer/app/DialtactsActivity.java index 4c57cda70..b2837769f 100644 --- a/java/com/android/dialer/app/DialtactsActivity.java +++ b/java/com/android/dialer/app/DialtactsActivity.java @@ -63,15 +63,14 @@ import android.widget.Toast; import com.android.contacts.common.dialog.ClearFrequentsDialog; import com.android.contacts.common.list.OnPhoneNumberPickerActionListener; import com.android.contacts.common.list.PhoneNumberListAdapter; -import com.android.contacts.common.list.PhoneNumberListAdapter.PhoneQuery; import com.android.contacts.common.list.PhoneNumberPickerFragment.CursorReranker; import com.android.contacts.common.list.PhoneNumberPickerFragment.OnLoadFinishedListener; import com.android.contacts.common.widget.FloatingActionButtonController; import com.android.dialer.animation.AnimUtils; import com.android.dialer.animation.AnimationListenerAdapter; +import com.android.dialer.app.calllog.CallLogActivity; import com.android.dialer.app.calllog.CallLogFragment; import com.android.dialer.app.calllog.CallLogNotificationsService; -import com.android.dialer.app.calllog.ClearCallLogDialog; import com.android.dialer.app.dialpad.DialpadFragment; import com.android.dialer.app.list.DragDropController; import com.android.dialer.app.list.ListsFragment; @@ -85,6 +84,7 @@ import com.android.dialer.app.list.SpeedDialFragment; import com.android.dialer.app.settings.DialerSettingsActivity; import com.android.dialer.app.widget.ActionBarController; import com.android.dialer.app.widget.SearchEditTextLayout; +import com.android.dialer.callcomposer.CallComposerActivity; import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.callintent.nano.CallSpecificAppData; import com.android.dialer.common.Assert; @@ -101,7 +101,10 @@ import com.android.dialer.p13n.inference.protocol.P13nRanker; import com.android.dialer.p13n.inference.protocol.P13nRanker.P13nRefreshCompleteListener; import com.android.dialer.p13n.logging.P13nLogger; import com.android.dialer.p13n.logging.P13nLogging; +import com.android.dialer.postcall.PostCall; import com.android.dialer.proguard.UsedByReflection; +import com.android.dialer.simulator.Simulator; +import com.android.dialer.simulator.SimulatorComponent; import com.android.dialer.smartdial.SmartDialNameMatcher; import com.android.dialer.smartdial.SmartDialPrefix; import com.android.dialer.telecom.TelecomUtil; @@ -124,7 +127,6 @@ public class DialtactsActivity extends TransactionSafeActivity OnListFragmentScrolledListener, CallLogFragment.HostInterface, DialpadFragment.HostInterface, - ListsFragment.HostInterface, SpeedDialFragment.HostInterface, SearchFragment.HostInterface, OnDragDropListener, @@ -478,6 +480,7 @@ public class DialtactsActivity extends TransactionSafeActivity @Override protected void onResume() { + LogUtil.d("DialtactsActivity.onResume", ""); Trace.beginSection(TAG + " onResume"); super.onResume(); @@ -490,6 +493,8 @@ public class DialtactsActivity extends TransactionSafeActivity } else if (mShowDialpadOnResume) { showDialpadFragment(false); mShowDialpadOnResume = false; + } else { + PostCall.promptUserForMessageIfNecessary(this, mParentLayout); } // If there was a voice query result returned in the {@link #onActivityResult} callback, it @@ -539,7 +544,7 @@ public class DialtactsActivity extends TransactionSafeActivity } if (getIntent().getBooleanExtra(EXTRA_CLEAR_NEW_VOICEMAILS, false)) { - CallLogNotificationsService.markNewVoicemailsAsOld(this); + CallLogNotificationsService.markNewVoicemailsAsOld(this, null); } setSearchBoxHint(); @@ -588,6 +593,7 @@ public class DialtactsActivity extends TransactionSafeActivity @Override public void onAttachFragment(final Fragment fragment) { + LogUtil.d("DialtactsActivity.onAttachFragment", "fragment: %s", fragment); if (fragment instanceof DialpadFragment) { mDialpadFragment = (DialpadFragment) fragment; if (!mIsDialpadShown && !mShowDialpadOnResume) { @@ -616,7 +622,8 @@ public class DialtactsActivity extends TransactionSafeActivity @MainThread public Cursor rerankCursor(Cursor data) { Assert.isMainThread(); - return mP13nRanker.rankCursor(data, PhoneQuery.PHONE_NUMBER); + String queryString = searchFragment.getQueryString(); + return mP13nRanker.rankCursor(data, queryString == null ? 0 : queryString.length()); } }); searchFragment.addOnLoadFinishedListener( @@ -674,9 +681,9 @@ public class DialtactsActivity extends TransactionSafeActivity } int resId = item.getItemId(); - if (item.getItemId() == R.id.menu_delete_all) { - ClearCallLogDialog.show(getFragmentManager()); - return true; + if (resId == R.id.menu_history) { + final Intent intent = new Intent(this, CallLogActivity.class); + startActivity(intent); } else if (resId == R.id.menu_clear_frequents) { ClearFrequentsDialog.show(getFragmentManager()); Logger.get(this).logScreenView(ScreenEvent.Type.CLEAR_FREQUENTS, this); @@ -691,6 +698,11 @@ public class DialtactsActivity extends TransactionSafeActivity @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + LogUtil.i( + "DialtactsActivity.onActivityResult", + "requestCode:%d, resultCode:%d", + requestCode, + resultCode); if (requestCode == ACTIVITY_REQUEST_CODE_VOICE_SEARCH) { if (resultCode == RESULT_OK) { final ArrayList<String> matches = @@ -701,15 +713,16 @@ public class DialtactsActivity extends TransactionSafeActivity LogUtil.i("DialtactsActivity.onActivityResult", "voice search - nothing heard"); } } else { - LogUtil.e("DialtactsActivity.onActivityResult", "voice search failed: " + resultCode); + LogUtil.e("DialtactsActivity.onActivityResult", "voice search failed"); } } else if (requestCode == ACTIVITY_REQUEST_CODE_CALL_COMPOSE) { - if (resultCode != RESULT_OK) { + if (resultCode == RESULT_FIRST_USER) { LogUtil.i( - "DialtactsActivity.onActivityResult", - "returned from call composer, error occurred (resultCode=" + resultCode + ")"); + "DialtactsActivity.onActivityResult", "returned from call composer, error occurred"); String message = - getString(R.string.call_composer_connection_failed, getString(R.string.share_and_call)); + getString( + R.string.call_composer_connection_failed, + data.getStringExtra(CallComposerActivity.KEY_CONTACT_NAME)); Snackbar.make(mParentLayout, message, Snackbar.LENGTH_LONG).show(); } else { LogUtil.i("DialtactsActivity.onActivityResult", "returned from call composer, no error"); @@ -732,6 +745,7 @@ public class DialtactsActivity extends TransactionSafeActivity * @see #onDialpadShown */ private void showDialpadFragment(boolean animate) { + LogUtil.d("DialtactActivity.showDialpadFragment", "animate: %b", animate); if (mIsDialpadShown || mStateSaved) { return; } @@ -767,6 +781,7 @@ public class DialtactsActivity extends TransactionSafeActivity /** Callback from child DialpadFragment when the dialpad is shown. */ public void onDialpadShown() { + LogUtil.d("DialtactsActivity.onDialpadShown", ""); Assert.isNotNull(mDialpadFragment); if (mDialpadFragment.getAnimate()) { Assert.isNotNull(mDialpadFragment.getView()).startAnimation(mSlideIn); @@ -838,12 +853,21 @@ public class DialtactsActivity extends TransactionSafeActivity private void updateSearchFragmentPosition() { SearchFragment fragment = null; - if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) { + if (mSmartDialSearchFragment != null) { fragment = mSmartDialSearchFragment; - } else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) { + } else if (mRegularSearchFragment != null) { fragment = mRegularSearchFragment; } - if (fragment != null && fragment.isVisible()) { + LogUtil.d( + "DialtactsActivity.updateSearchFragmentPosition", + "fragment: %s, isVisible: %b", + fragment, + fragment != null && fragment.isVisible()); + if (fragment != null) { + // We need to force animation here even when fragment is not visible since it might not be + // visible immediately after screen orientation change and dialpad height would not be + // available immediately which is required to update position. By forcing an animation, + // position will be updated after a delay by when the dialpad height would be available. fragment.updatePosition(true /* animate */); } } @@ -858,11 +882,6 @@ public class DialtactsActivity extends TransactionSafeActivity return !TextUtils.isEmpty(mSearchQuery); } - @Override - public boolean shouldShowActionBar() { - return mListsFragment.shouldShowActionBar(); - } - private void setNotInSearchUi() { mInDialpadSearch = false; mInRegularSearch = false; @@ -1056,7 +1075,8 @@ public class DialtactsActivity extends TransactionSafeActivity } // DialtactsActivity will provide the options menu fragment.setHasOptionsMenu(false); - fragment.setShowEmptyListForNullQuery(true); + // Will show empty list if P13nRanker is not enabled. Else, re-ranked list by the ranker. + fragment.setShowEmptyListForNullQuery(mP13nRanker.shouldShowEmptyListForNullQuery()); if (!smartDialSearch) { fragment.setQueryString(query); } @@ -1361,11 +1381,6 @@ public class DialtactsActivity extends TransactionSafeActivity } @Override - public ActionBarController getActionBarController() { - return mActionBarController; - } - - @Override public boolean isDialpadShown() { return mIsDialpadShown; } @@ -1379,11 +1394,6 @@ public class DialtactsActivity extends TransactionSafeActivity } @Override - public int getActionBarHideOffset() { - return getActionBarSafely().getHideOffset(); - } - - @Override public void setActionBarHideOffset(int offset) { getActionBarSafely().setHideOffset(offset); } @@ -1461,8 +1471,19 @@ public class DialtactsActivity extends TransactionSafeActivity && mListsFragment.getSpeedDialFragment().hasFrequents() && hasContactsPermission); - menu.findItem(R.id.menu_delete_all) + menu.findItem(R.id.menu_history) .setVisible(PermissionsUtil.hasPhonePermissions(DialtactsActivity.this)); + + Context context = DialtactsActivity.this.getApplicationContext(); + MenuItem simulatorMenuItem = menu.findItem(R.id.menu_simulator_submenu); + Simulator simulator = SimulatorComponent.get(context).getSimulator(); + if (simulator.shouldShow()) { + simulatorMenuItem.setVisible(true); + simulatorMenuItem.setActionProvider(simulator.getActionProvider(context)); + } else { + simulatorMenuItem.setVisible(false); + } + super.show(); } } diff --git a/java/com/android/dialer/app/PhoneCallDetails.java b/java/com/android/dialer/app/PhoneCallDetails.java deleted file mode 100644 index 436f68eec..000000000 --- a/java/com/android/dialer/app/PhoneCallDetails.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (C) 2011 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.app; - -import android.content.Context; -import android.content.res.Resources; -import android.net.Uri; -import android.provider.CallLog; -import android.provider.CallLog.Calls; -import android.support.annotation.Nullable; -import android.telecom.PhoneAccountHandle; -import android.text.TextUtils; -import com.android.contacts.common.ContactsUtils.UserType; -import com.android.contacts.common.preference.ContactsPreferences; -import com.android.contacts.common.util.ContactDisplayUtils; -import com.android.dialer.app.calllog.PhoneNumberDisplayUtil; -import com.android.dialer.phonenumbercache.ContactInfo; - -/** The details of a phone call to be shown in the UI. */ -public class PhoneCallDetails { - - // The number of the other party involved in the call. - public CharSequence number; - // Post-dial digits associated with the outgoing call. - public String postDialDigits; - // The secondary line number the call was received via. - public String viaNumber; - // The number presenting rules set by the network, e.g., {@link Calls#PRESENTATION_ALLOWED} - public int numberPresentation; - // The country corresponding with the phone number. - public String countryIso; - // The geocoded location for the phone number. - public String geocode; - - /** - * The type of calls, as defined in the call log table, e.g., {@link Calls#INCOMING_TYPE}. - * - * <p>There might be multiple types if this represents a set of entries grouped together. - */ - public int[] callTypes; - - // The date of the call, in milliseconds since the epoch. - public long date; - // The duration of the call in milliseconds, or 0 for missed calls. - public long duration; - // The name of the contact, or the empty string. - public CharSequence namePrimary; - // The alternative name of the contact, e.g. last name first, or the empty string - public CharSequence nameAlternative; - /** - * The user's preference on name display order, last name first or first time first. {@see - * ContactsPreferences} - */ - public int nameDisplayOrder; - // The type of phone, e.g., {@link Phone#TYPE_HOME}, 0 if not available. - public int numberType; - // The custom label associated with the phone number in the contact, or the empty string. - public CharSequence numberLabel; - // The URI of the contact associated with this phone call. - public Uri contactUri; - - /** - * The photo URI of the picture of the contact that is associated with this phone call or null if - * there is none. - * - * <p>This is meant to store the high-res photo only. - */ - public Uri photoUri; - - // The source type of the contact associated with this call. - public int sourceType; - - // The object id type of the contact associated with this call. - public String objectId; - - // The unique identifier for the account associated with the call. - public PhoneAccountHandle accountHandle; - - // Features applicable to this call. - public int features; - - // Total data usage for this call. - public Long dataUsage; - - // Voicemail transcription - public String transcription; - - // The display string for the number. - public String displayNumber; - - // Whether the contact number is a voicemail number. - public boolean isVoicemail; - - /** The {@link UserType} of the contact */ - public @UserType long contactUserType; - - /** - * If this is a voicemail, whether the message is read. For other types of calls, this defaults to - * {@code true}. - */ - public boolean isRead = true; - - // If this call is a spam number. - public boolean isSpam = false; - - // If this call is a blocked number. - public boolean isBlocked = false; - - // Call location and date text. - public CharSequence callLocationAndDate; - - // Call description. - public CharSequence callDescription; - public String accountComponentName; - public String accountId; - public ContactInfo cachedContactInfo; - public int voicemailId; - public int previousGroup; - - /** - * Constructor with required fields for the details of a call with a number associated with a - * contact. - */ - public PhoneCallDetails( - CharSequence number, int numberPresentation, CharSequence postDialDigits) { - this.number = number; - this.numberPresentation = numberPresentation; - this.postDialDigits = postDialDigits.toString(); - } - /** - * Construct the "on {accountLabel} via {viaNumber}" accessibility description for the account - * list item, depending on the existence of the accountLabel and viaNumber. - * - * @param viaNumber The number that this call is being placed via. - * @param accountLabel The {@link PhoneAccount} label that this call is being placed with. - * @return The description of the account that this call has been placed on. - */ - public static CharSequence createAccountLabelDescription( - Resources resources, @Nullable String viaNumber, @Nullable CharSequence accountLabel) { - - if ((!TextUtils.isEmpty(viaNumber)) && !TextUtils.isEmpty(accountLabel)) { - String msg = - resources.getString( - R.string.description_via_number_phone_account, accountLabel, viaNumber); - CharSequence accountNumberLabel = - ContactDisplayUtils.getTelephoneTtsSpannable(msg, viaNumber); - return (accountNumberLabel == null) ? msg : accountNumberLabel; - } else if (!TextUtils.isEmpty(viaNumber)) { - CharSequence viaNumberLabel = - ContactDisplayUtils.getTtsSpannedPhoneNumber( - resources, R.string.description_via_number, viaNumber); - return (viaNumberLabel == null) ? viaNumber : viaNumberLabel; - } else if (!TextUtils.isEmpty(accountLabel)) { - return TextUtils.expandTemplate( - resources.getString(R.string.description_phone_account), accountLabel); - } - return ""; - } - - /** - * Returns the preferred name for the call details as specified by the {@link #nameDisplayOrder} - * - * @return the preferred name - */ - public CharSequence getPreferredName() { - if (nameDisplayOrder == ContactsPreferences.DISPLAY_ORDER_PRIMARY - || TextUtils.isEmpty(nameAlternative)) { - return namePrimary; - } - return nameAlternative; - } - - public void updateDisplayNumber( - Context context, CharSequence formattedNumber, boolean isVoicemail) { - displayNumber = - PhoneNumberDisplayUtil.getDisplayNumber( - context, number, numberPresentation, formattedNumber, postDialDigits, isVoicemail) - .toString(); - } - - public boolean hasIncomingCalls() { - for (int i = 0; i < callTypes.length; i++) { - if (callTypes[i] == CallLog.Calls.INCOMING_TYPE - || callTypes[i] == CallLog.Calls.MISSED_TYPE - || callTypes[i] == CallLog.Calls.VOICEMAIL_TYPE - || callTypes[i] == CallLog.Calls.REJECTED_TYPE - || callTypes[i] == CallLog.Calls.BLOCKED_TYPE) { - return true; - } - } - return false; - } -} diff --git a/java/com/android/dialer/app/SpecialCharSequenceMgr.java b/java/com/android/dialer/app/SpecialCharSequenceMgr.java index 2ae19704a..712659c12 100644 --- a/java/com/android/dialer/app/SpecialCharSequenceMgr.java +++ b/java/com/android/dialer/app/SpecialCharSequenceMgr.java @@ -28,15 +28,14 @@ import android.content.DialogInterface; import android.content.Intent; import android.database.Cursor; import android.net.Uri; -import android.os.Looper; import android.provider.Settings; import android.support.annotation.Nullable; +import android.support.v4.os.BuildCompat; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telephony.PhoneNumberUtils; import android.telephony.TelephonyManager; import android.text.TextUtils; -import android.util.Log; import android.view.WindowManager; import android.widget.EditText; import android.widget.Toast; @@ -46,8 +45,11 @@ import com.android.contacts.common.database.NoNullCursorAsyncQueryHandler; import com.android.contacts.common.util.ContactDisplayUtils; import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment; import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener; -import com.android.dialer.app.calllog.PhoneAccountUtils; +import com.android.dialer.calllogutils.PhoneAccountUtils; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; import com.android.dialer.compat.CompatUtils; +import com.android.dialer.oem.MotorolaUtils; import com.android.dialer.telecom.TelecomUtil; import java.util.ArrayList; import java.util.List; @@ -100,12 +102,19 @@ public class SpecialCharSequenceMgr { //get rid of the separators so that the string gets parsed correctly String dialString = PhoneNumberUtils.stripSeparators(input); - return handleDeviceIdDisplay(context, dialString) + if (handleDeviceIdDisplay(context, dialString) || handleRegulatoryInfoDisplay(context, dialString) || handlePinEntry(context, dialString) || handleAdnEntry(context, dialString, textField) - || handleSecretCode(context, dialString); + || handleSecretCode(context, dialString)) { + return true; + } + + if (MotorolaUtils.handleSpecialCharSequence(context, input)) { + return true; + } + return false; } /** @@ -114,10 +123,7 @@ public class SpecialCharSequenceMgr { * <p>This should be called when the screen becomes background. */ public static void cleanup() { - if (Looper.myLooper() != Looper.getMainLooper()) { - Log.wtf(TAG, "cleanup() is called outside the main thread"); - return; - } + Assert.isMainThread(); if (sPreviousAdnQueryHandler != null) { sPreviousAdnQueryHandler.cancel(); @@ -126,14 +132,21 @@ public class SpecialCharSequenceMgr { } /** - * Handles secret codes to launch arbitrary activities in the form of *#*#<code>#*#*. If a secret - * code is encountered an Intent is started with the android_secret_code://<code> URI. + * Handles secret codes to launch arbitrary activities in the form of *#*#<code>#*#*. + * If a secret code is encountered, an Intent is started with the android_secret_code://<code> + * URI. * * @param context the context to use * @param input the text to check for a secret code in - * @return true if a secret code was encountered + * @return true if a secret code was encountered and intent is sent out */ static boolean handleSecretCode(Context context, String input) { + // Must use system service on O+ to avoid using broadcasts, which are not allowed on O+. + if (BuildCompat.isAtLeastO()) { + return context.getSystemService(TelephonyManager.class).sendDialerCode(input); + } + + // System service call is not supported pre-O, so must use a broadcast for N-. // Secret codes are in the form *#*#<code>#*#* int len = input.length(); if (len > 8 && input.startsWith("*#*#") && input.endsWith("#*#*")) { @@ -144,7 +157,6 @@ public class SpecialCharSequenceMgr { context.sendBroadcast(intent); return true; } - return false; } @@ -237,7 +249,7 @@ public class SpecialCharSequenceMgr { private static void handleAdnQuery(QueryHandler handler, SimContactQueryCookie cookie, Uri uri) { if (handler == null || cookie == null || uri == null) { - Log.w(TAG, "queryAdn parameters incorrect"); + LogUtil.w("SpecialCharSequenceMgr.handleAdnQuery", "queryAdn parameters incorrect"); return; } @@ -325,12 +337,14 @@ public class SpecialCharSequenceMgr { private static boolean handleRegulatoryInfoDisplay(Context context, String input) { if (input.equals(MMI_REGULATORY_INFO_DISPLAY)) { - Log.d(TAG, "handleRegulatoryInfoDisplay() sending intent to settings app"); + LogUtil.i( + "SpecialCharSequenceMgr.handleRegulatoryInfoDisplay", "sending intent to settings app"); Intent showRegInfoIntent = new Intent(Settings.ACTION_SHOW_REGULATORY_INFO); try { context.startActivity(showRegInfoIntent); } catch (ActivityNotFoundException e) { - Log.e(TAG, "startActivity() failed: " + e); + LogUtil.e( + "SpecialCharSequenceMgr.handleRegulatoryInfoDisplay", "startActivity() failed: ", e); } return true; } diff --git a/java/com/android/dialer/app/calllog/CallDetailHistoryAdapter.java b/java/com/android/dialer/app/calllog/CallDetailHistoryAdapter.java deleted file mode 100644 index ab6ef7362..000000000 --- a/java/com/android/dialer/app/calllog/CallDetailHistoryAdapter.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (C) 2011 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.app.calllog; - -import android.content.Context; -import android.icu.lang.UCharacter; -import android.icu.text.BreakIterator; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; -import android.provider.CallLog.Calls; -import android.text.format.DateUtils; -import android.text.format.Formatter; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.TextView; -import com.android.dialer.app.PhoneCallDetails; -import com.android.dialer.app.R; -import com.android.dialer.util.CallUtil; -import com.android.dialer.util.DialerUtils; -import java.util.ArrayList; -import java.util.Locale; - -/** Adapter for a ListView containing history items from the details of a call. */ -public class CallDetailHistoryAdapter extends BaseAdapter { - - /** Each history item shows the detail of a call. */ - private static final int VIEW_TYPE_HISTORY_ITEM = 1; - - private final Context mContext; - private final LayoutInflater mLayoutInflater; - private final CallTypeHelper mCallTypeHelper; - private final PhoneCallDetails[] mPhoneCallDetails; - - /** List of items to be concatenated together for duration strings. */ - private ArrayList<CharSequence> mDurationItems = new ArrayList<>(); - - public CallDetailHistoryAdapter( - Context context, - LayoutInflater layoutInflater, - CallTypeHelper callTypeHelper, - PhoneCallDetails[] phoneCallDetails) { - mContext = context; - mLayoutInflater = layoutInflater; - mCallTypeHelper = callTypeHelper; - mPhoneCallDetails = phoneCallDetails; - } - - @Override - public boolean isEnabled(int position) { - // None of history will be clickable. - return false; - } - - @Override - public int getCount() { - return mPhoneCallDetails.length; - } - - @Override - public Object getItem(int position) { - return mPhoneCallDetails[position]; - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public int getViewTypeCount() { - return 1; - } - - @Override - public int getItemViewType(int position) { - return VIEW_TYPE_HISTORY_ITEM; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - // Make sure we have a valid convertView to start with - final View result = - convertView == null - ? mLayoutInflater.inflate(R.layout.call_detail_history_item, parent, false) - : convertView; - - PhoneCallDetails details = mPhoneCallDetails[position]; - CallTypeIconsView callTypeIconView = - (CallTypeIconsView) result.findViewById(R.id.call_type_icon); - TextView callTypeTextView = (TextView) result.findViewById(R.id.call_type_text); - TextView dateView = (TextView) result.findViewById(R.id.date); - TextView durationView = (TextView) result.findViewById(R.id.duration); - - int callType = details.callTypes[0]; - boolean isVideoCall = - (details.features & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO - && CallUtil.isVideoEnabled(mContext); - boolean isPulledCall = - (details.features & Calls.FEATURES_PULLED_EXTERNALLY) == Calls.FEATURES_PULLED_EXTERNALLY; - - callTypeIconView.clear(); - callTypeIconView.add(callType); - callTypeIconView.setShowVideo(isVideoCall); - callTypeTextView.setText(mCallTypeHelper.getCallTypeText(callType, isVideoCall, isPulledCall)); - // Set the date. - dateView.setText(formatDate(details.date)); - // Set the duration - if (Calls.VOICEMAIL_TYPE == callType || CallTypeHelper.isMissedCallType(callType)) { - durationView.setVisibility(View.GONE); - } else { - durationView.setVisibility(View.VISIBLE); - durationView.setText(formatDurationAndDataUsage(details.duration, details.dataUsage)); - } - - return result; - } - - /** - * Formats the provided date into a value suitable for display in the current locale. - * - * <p>For example, returns a string like "Wednesday, May 25, 2016, 8:02PM" or "Chorshanba, 2016 - * may 25,20:02". - * - * <p>For pre-N devices, the returned value may not start with a capital if the local convention - * is to not capitalize day names. On N+ devices, the returned value is always capitalized. - */ - private CharSequence formatDate(long callDateMillis) { - CharSequence dateValue = - DateUtils.formatDateRange( - mContext, - callDateMillis /* startDate */, - callDateMillis /* endDate */, - DateUtils.FORMAT_SHOW_TIME - | DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_SHOW_WEEKDAY - | DateUtils.FORMAT_SHOW_YEAR); - - // We want the beginning of the date string to be capitalized, even if the word at the beginning - // of the string is not usually capitalized. For example, "Wednesdsay" in Uzbek is "chorshanba” - // (not capitalized). To handle this issue we apply title casing to the start of the sentence so - // that "chorshanba, 2016 may 25,20:02" becomes "Chorshanba, 2016 may 25,20:02". - // - // The ICU library was not available in Android until N, so we can only do this in N+ devices. - // Pre-N devices will still see incorrect capitalization in some languages. - if (VERSION.SDK_INT < VERSION_CODES.N) { - return dateValue; - } - - // Using the ICU library is safer than just applying toUpperCase() on the first letter of the - // word because in some languages, there can be multiple starting characters which should be - // upper-cased together. For example in Dutch "ij" is a digraph in which both letters should be - // capitalized together. - - // TITLECASE_NO_LOWERCASE is necessary so that things that are already capitalized like the - // month ("May") are not lower-cased as part of the conversion. - return UCharacter.toTitleCase( - Locale.getDefault(), - dateValue.toString(), - BreakIterator.getSentenceInstance(), - UCharacter.TITLECASE_NO_LOWERCASE); - } - - private CharSequence formatDuration(long elapsedSeconds) { - long minutes = 0; - long seconds = 0; - - if (elapsedSeconds >= 60) { - minutes = elapsedSeconds / 60; - elapsedSeconds -= minutes * 60; - seconds = elapsedSeconds; - return mContext.getString(R.string.callDetailsDurationFormat, minutes, seconds); - } else { - seconds = elapsedSeconds; - return mContext.getString(R.string.callDetailsShortDurationFormat, seconds); - } - } - - /** - * Formats a string containing the call duration and the data usage (if specified). - * - * @param elapsedSeconds Total elapsed seconds. - * @param dataUsage Data usage in bytes, or null if not specified. - * @return String containing call duration and data usage. - */ - private CharSequence formatDurationAndDataUsage(long elapsedSeconds, Long dataUsage) { - CharSequence duration = formatDuration(elapsedSeconds); - - if (dataUsage != null) { - mDurationItems.clear(); - mDurationItems.add(duration); - mDurationItems.add(Formatter.formatShortFileSize(mContext, dataUsage)); - - return DialerUtils.join(mDurationItems); - } else { - return duration; - } - } -} diff --git a/java/com/android/dialer/app/calllog/CallLogActivity.java b/java/com/android/dialer/app/calllog/CallLogActivity.java new file mode 100644 index 000000000..719ab4369 --- /dev/null +++ b/java/com/android/dialer/app/calllog/CallLogActivity.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2013 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.app.calllog; + +import android.app.Fragment; +import android.app.FragmentManager; +import android.content.Intent; +import android.os.Bundle; +import android.provider.CallLog; +import android.provider.CallLog.Calls; +import android.support.v13.app.FragmentPagerAdapter; +import android.support.v4.view.ViewPager; +import android.support.v7.app.ActionBar; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.ViewGroup; +import com.android.contacts.common.list.ViewPagerTabs; +import com.android.dialer.app.DialtactsActivity; +import com.android.dialer.app.R; +import com.android.dialer.database.CallLogQueryHandler; +import com.android.dialer.logging.Logger; +import com.android.dialer.logging.nano.ScreenEvent; +import com.android.dialer.util.TransactionSafeActivity; +import com.android.dialer.util.ViewUtil; + +/** Activity for viewing call history. */ +public class CallLogActivity extends TransactionSafeActivity + implements ViewPager.OnPageChangeListener { + + private static final int TAB_INDEX_ALL = 0; + private static final int TAB_INDEX_MISSED = 1; + private static final int TAB_INDEX_COUNT = 2; + private ViewPager mViewPager; + private ViewPagerTabs mViewPagerTabs; + private ViewPagerAdapter mViewPagerAdapter; + private CallLogFragment mAllCallsFragment; + private CallLogFragment mMissedCallsFragment; + private String[] mTabTitles; + private boolean mIsResumed; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.call_log_activity); + getWindow().setBackgroundDrawable(null); + + final ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayShowHomeEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setDisplayShowTitleEnabled(true); + actionBar.setElevation(0); + + int startingTab = TAB_INDEX_ALL; + final Intent intent = getIntent(); + if (intent != null) { + final int callType = intent.getIntExtra(CallLog.Calls.EXTRA_CALL_TYPE_FILTER, -1); + if (callType == CallLog.Calls.MISSED_TYPE) { + startingTab = TAB_INDEX_MISSED; + } + } + + mTabTitles = new String[TAB_INDEX_COUNT]; + mTabTitles[0] = getString(R.string.call_log_all_title); + mTabTitles[1] = getString(R.string.call_log_missed_title); + + mViewPager = (ViewPager) findViewById(R.id.call_log_pager); + + mViewPagerAdapter = new ViewPagerAdapter(getFragmentManager()); + mViewPager.setAdapter(mViewPagerAdapter); + mViewPager.setOffscreenPageLimit(1); + mViewPager.setOnPageChangeListener(this); + + mViewPagerTabs = (ViewPagerTabs) findViewById(R.id.viewpager_header); + + mViewPagerTabs.setViewPager(mViewPager); + mViewPager.setCurrentItem(startingTab); + } + + @Override + protected void onResume() { + mIsResumed = true; + super.onResume(); + sendScreenViewForChildFragment(mViewPager.getCurrentItem()); + } + + @Override + protected void onPause() { + mIsResumed = false; + super.onPause(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + final MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.call_log_options, menu); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + final MenuItem itemDeleteAll = menu.findItem(R.id.delete_all); + if (mAllCallsFragment != null && itemDeleteAll != null) { + // If onPrepareOptionsMenu is called before fragments are loaded, don't do anything. + final CallLogAdapter adapter = mAllCallsFragment.getAdapter(); + itemDeleteAll.setVisible(adapter != null && !adapter.isEmpty()); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (!isSafeToCommitTransactions()) { + return true; + } + + if (item.getItemId() == android.R.id.home) { + final Intent intent = new Intent(this, DialtactsActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + return true; + } else if (item.getItemId() == R.id.delete_all) { + ClearCallLogDialog.show(getFragmentManager()); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + mViewPagerTabs.onPageScrolled(position, positionOffset, positionOffsetPixels); + } + + @Override + public void onPageSelected(int position) { + if (mIsResumed) { + sendScreenViewForChildFragment(position); + } + mViewPagerTabs.onPageSelected(position); + } + + @Override + public void onPageScrollStateChanged(int state) { + mViewPagerTabs.onPageScrollStateChanged(state); + } + + private void sendScreenViewForChildFragment(int position) { + Logger.get(this).logScreenView(ScreenEvent.Type.CALL_LOG_FILTER, this); + } + + private int getRtlPosition(int position) { + if (ViewUtil.isRtl()) { + return mViewPagerAdapter.getCount() - 1 - position; + } + return position; + } + + /** Adapter for the view pager. */ + public class ViewPagerAdapter extends FragmentPagerAdapter { + + public ViewPagerAdapter(FragmentManager fm) { + super(fm); + } + + @Override + public long getItemId(int position) { + return getRtlPosition(position); + } + + @Override + public Fragment getItem(int position) { + switch (getRtlPosition(position)) { + case TAB_INDEX_ALL: + return new CallLogFragment( + CallLogQueryHandler.CALL_TYPE_ALL, true /* isCallLogActivity */); + case TAB_INDEX_MISSED: + return new CallLogFragment(Calls.MISSED_TYPE, true /* isCallLogActivity */); + } + throw new IllegalStateException("No fragment at position " + position); + } + + @Override + public Object instantiateItem(ViewGroup container, int position) { + final CallLogFragment fragment = (CallLogFragment) super.instantiateItem(container, position); + switch (position) { + case TAB_INDEX_ALL: + mAllCallsFragment = fragment; + break; + case TAB_INDEX_MISSED: + mMissedCallsFragment = fragment; + break; + } + return fragment; + } + + @Override + public CharSequence getPageTitle(int position) { + return mTabTitles[position]; + } + + @Override + public int getCount() { + return TAB_INDEX_COUNT; + } + } +} diff --git a/java/com/android/dialer/app/calllog/CallLogAdapter.java b/java/com/android/dialer/app/calllog/CallLogAdapter.java index ea09a8c0a..fc5ffbb29 100644 --- a/java/com/android/dialer/app/calllog/CallLogAdapter.java +++ b/java/com/android/dialer/app/calllog/CallLogAdapter.java @@ -47,7 +47,6 @@ import com.android.contacts.common.compat.PhoneNumberUtilsCompat; import com.android.contacts.common.preference.ContactsPreferences; import com.android.dialer.app.Bindings; import com.android.dialer.app.DialtactsActivity; -import com.android.dialer.app.PhoneCallDetails; import com.android.dialer.app.R; import com.android.dialer.app.calllog.CallLogGroupBuilder.GroupCreator; import com.android.dialer.app.calllog.calllogcache.CallLogCache; @@ -55,13 +54,19 @@ import com.android.dialer.app.contactinfo.ContactInfoCache; import com.android.dialer.app.voicemail.VoicemailPlaybackPresenter; import com.android.dialer.app.voicemail.VoicemailPlaybackPresenter.OnVoicemailDeletedListener; import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler; +import com.android.dialer.calldetails.nano.CallDetailsEntries; +import com.android.dialer.calldetails.nano.CallDetailsEntries.CallDetailsEntry; +import com.android.dialer.calllogutils.PhoneAccountUtils; +import com.android.dialer.calllogutils.PhoneCallDetails; import com.android.dialer.common.Assert; import com.android.dialer.common.AsyncTaskExecutor; import com.android.dialer.common.AsyncTaskExecutors; import com.android.dialer.common.LogUtil; import com.android.dialer.enrichedcall.EnrichedCallCapabilities; +import com.android.dialer.enrichedcall.EnrichedCallComponent; import com.android.dialer.enrichedcall.EnrichedCallManager; import com.android.dialer.enrichedcall.EnrichedCallManager.CapabilitiesListener; +import com.android.dialer.enrichedcall.historyquery.proto.nano.HistoryResult; import com.android.dialer.logging.Logger; import com.android.dialer.logging.nano.DialerImpression; import com.android.dialer.phonenumbercache.CallLogQuery; @@ -70,6 +75,7 @@ import com.android.dialer.phonenumbercache.ContactInfoHelper; import com.android.dialer.phonenumberutil.PhoneNumberHelper; import com.android.dialer.spam.Spam; import com.android.dialer.util.PermissionsUtil; +import java.util.List; import java.util.Map; import java.util.Set; @@ -96,7 +102,7 @@ public class CallLogAdapter extends GroupingListAdapter protected final CallLogCache mCallLogCache; private final CallFetcher mCallFetcher; - private final FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler; + @NonNull private final FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler; private final int mActivityType; /** Instance of helper class for managing views. */ @@ -182,8 +188,6 @@ public class CallLogAdapter extends GroupingListAdapter private boolean mIsSpamEnabled; - @NonNull private final EnrichedCallManager mEnrichedCallManager; - public CallLogAdapter( Activity activity, ViewGroup alertContainer, @@ -191,6 +195,7 @@ public class CallLogAdapter extends GroupingListAdapter CallLogCache callLogCache, ContactInfoCache contactInfoCache, VoicemailPlaybackPresenter voicemailPlaybackPresenter, + @NonNull FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler, int activityType) { super(); @@ -218,7 +223,7 @@ public class CallLogAdapter extends GroupingListAdapter mCallLogListItemHelper = new CallLogListItemHelper(phoneCallDetailsHelper, resources, mCallLogCache); mCallLogGroupBuilder = new CallLogGroupBuilder(this); - mFilteredNumberAsyncQueryHandler = new FilteredNumberAsyncQueryHandler(mActivity); + mFilteredNumberAsyncQueryHandler = Assert.isNotNull(filteredNumberAsyncQueryHandler); mContactsPreferences = new ContactsPreferences(mActivity); @@ -232,7 +237,6 @@ public class CallLogAdapter extends GroupingListAdapter mCallLogAlertManager = new CallLogAlertManager(this, LayoutInflater.from(mActivity), alertContainer); - mEnrichedCallManager = EnrichedCallManager.Accessor.getInstance(activity.getApplication()); } private void expandViewHolderActions(CallLogListItemViewHolder viewHolder) { @@ -296,7 +300,7 @@ public class CallLogAdapter extends GroupingListAdapter } mContactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY); mIsSpamEnabled = Spam.get(mActivity).isSpamEnabled(); - mEnrichedCallManager.registerCapabilitiesListener(this); + getEnrichedCallManager().registerCapabilitiesListener(this); notifyDataSetChanged(); } @@ -305,11 +309,11 @@ public class CallLogAdapter extends GroupingListAdapter for (Uri uri : mHiddenItemUris) { CallLogAsyncTaskUtil.deleteVoicemail(mActivity, uri, null); } - mEnrichedCallManager.unregisterCapabilitiesListener(this); + getEnrichedCallManager().unregisterCapabilitiesListener(this); } public void onStop() { - mEnrichedCallManager.clearCachedData(); + getEnrichedCallManager().clearCachedData(); } public CallLogAlertManager getAlertManager() { @@ -420,7 +424,9 @@ public class CallLogAdapter extends GroupingListAdapter } CallLogListItemViewHolder views = (CallLogListItemViewHolder) viewHolder; views.isLoaded = false; - PhoneCallDetails details = createPhoneCallDetails(c, getGroupSize(position), views); + int groupSize = getGroupSize(position); + CallDetailsEntries callDetailsEntries = createCallDetailsEntries(c, groupSize); + PhoneCallDetails details = createPhoneCallDetails(c, groupSize, views); if (mHiddenRowIds.contains(c.getLong(CallLogQuery.ID))) { views.callLogEntryView.setVisibility(View.GONE); views.dayGroupHeader.setVisibility(View.GONE); @@ -432,11 +438,14 @@ public class CallLogAdapter extends GroupingListAdapter if (mCurrentlyExpandedRowId == views.rowId) { views.inflateActionViewStub(); } - loadAndRender(views, views.rowId, details); + loadAndRender(views, views.rowId, details, callDetailsEntries); } private void loadAndRender( - final CallLogListItemViewHolder views, final long rowId, final PhoneCallDetails details) { + final CallLogListItemViewHolder views, + final long rowId, + final PhoneCallDetails details, + final CallDetailsEntries callDetailsEntries) { // Reset block and spam information since this view could be reused which may contain // outdated data. views.isSpam = false; @@ -464,12 +473,33 @@ public class CallLogAdapter extends GroupingListAdapter && Spam.get(mActivity) .checkSpamStatusSynchronous(views.number, views.countryIso); details.isSpam = views.isSpam; - if (isCancelled()) { - return false; + } + if (isCancelled()) { + return false; + } + setCallDetailsEntriesHistoryResults( + PhoneNumberUtils.formatNumberToE164(views.number, views.countryIso), + callDetailsEntries); + views.setDetailedPhoneDetails(callDetailsEntries); + return !isCancelled() && loadData(views, rowId, details); + } + + private void setCallDetailsEntriesHistoryResults( + @Nullable String number, CallDetailsEntries callDetailsEntries) { + if (number == null) { + return; + } + Map<CallDetailsEntry, List<HistoryResult>> mappedResults = + getEnrichedCallManager().getAllHistoricalData(number, callDetailsEntries); + for (CallDetailsEntry entry : callDetailsEntries.entries) { + List<HistoryResult> results = mappedResults.get(entry); + if (results != null) { + entry.historyResults = mappedResults.get(entry).toArray(new HistoryResult[0]); + LogUtil.v( + "CallLogAdapter.setCallDetailsEntriesHistoryResults", + "mapped %d results", + entry.historyResults.length); } - return loadData(views, rowId, details); - } else { - return loadData(views, rowId, details); } } @@ -499,9 +529,9 @@ public class CallLogAdapter extends GroupingListAdapter return false; } - EnrichedCallCapabilities capabilities = mEnrichedCallManager.getCapabilities(e164Number); + EnrichedCallCapabilities capabilities = getEnrichedCallManager().getCapabilities(e164Number); if (capabilities == null) { - mEnrichedCallManager.requestCapabilities(e164Number); + getEnrichedCallManager().requestCapabilities(e164Number); return false; } return capabilities.supportsCallComposer(); @@ -562,6 +592,27 @@ public class CallLogAdapter extends GroupingListAdapter return details; } + @MainThread + private static CallDetailsEntries createCallDetailsEntries(Cursor cursor, int count) { + Assert.isMainThread(); + int position = cursor.getPosition(); + CallDetailsEntries entries = new CallDetailsEntries(); + entries.entries = new CallDetailsEntry[count]; + for (int i = 0; i < count; i++) { + CallDetailsEntry entry = new CallDetailsEntry(); + entry.callId = cursor.getLong(CallLogQuery.ID); + entry.callType = cursor.getInt(CallLogQuery.CALL_TYPE); + entry.dataUsage = cursor.getLong(CallLogQuery.DATA_USAGE); + entry.date = cursor.getLong(CallLogQuery.DATE); + entry.duration = cursor.getLong(CallLogQuery.DURATION); + entry.features |= cursor.getInt(CallLogQuery.FEATURES); + entries.entries[i] = entry; + cursor.moveToNext(); + } + cursor.moveToPosition(position); + return entries; + } + /** * Load data for call log. Any expensive operation should be put here to avoid blocking main * thread. Do NOT put any cursor operation here since it's not thread safe. @@ -907,6 +958,11 @@ public class CallLogAdapter extends GroupingListAdapter notifyDataSetChanged(); } + @NonNull + private EnrichedCallManager getEnrichedCallManager() { + return EnrichedCallComponent.get(mActivity).getEnrichedCallManager(); + } + /** Interface used to initiate a refresh of the content. */ public interface CallFetcher { diff --git a/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java b/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java index b4e6fc5ad..2198626d6 100644 --- a/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java +++ b/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java @@ -16,37 +16,22 @@ package com.android.dialer.app.calllog; -import android.Manifest.permission; import android.annotation.TargetApi; import android.content.ContentValues; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; -import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; -import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.provider.CallLog; import android.provider.VoicemailContract.Voicemails; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; -import android.support.v4.content.ContextCompat; -import android.telecom.PhoneAccountHandle; import android.text.TextUtils; -import com.android.contacts.common.GeoUtil; -import com.android.dialer.app.PhoneCallDetails; import com.android.dialer.common.AsyncTaskExecutor; import com.android.dialer.common.AsyncTaskExecutors; -import com.android.dialer.common.LogUtil; -import com.android.dialer.phonenumbercache.ContactInfo; -import com.android.dialer.phonenumbercache.ContactInfoHelper; -import com.android.dialer.phonenumberutil.PhoneNumberHelper; -import com.android.dialer.telecom.TelecomUtil; import com.android.dialer.util.PermissionsUtil; -import java.util.ArrayList; -import java.util.Arrays; +import com.android.voicemail.VoicemailClient; @TargetApi(VERSION_CODES.M) public class CallLogAsyncTaskUtil { @@ -58,166 +43,6 @@ public class CallLogAsyncTaskUtil { sAsyncTaskExecutor = AsyncTaskExecutors.createThreadPoolExecutor(); } - public static void getCallDetails( - @NonNull final Context context, - @Nullable final CallLogAsyncTaskListener callLogAsyncTaskListener, - @NonNull final Uri... callUris) { - if (sAsyncTaskExecutor == null) { - initTaskExecutor(); - } - - sAsyncTaskExecutor.submit( - Tasks.GET_CALL_DETAILS, - new AsyncTask<Void, Void, PhoneCallDetails[]>() { - @Override - public PhoneCallDetails[] doInBackground(Void... params) { - if (ContextCompat.checkSelfPermission(context, permission.READ_CALL_LOG) - != PackageManager.PERMISSION_GRANTED) { - LogUtil.w("CallLogAsyncTaskUtil.getCallDetails", "missing READ_CALL_LOG permission"); - return null; - } - // TODO: All calls correspond to the same person, so make a single lookup. - final int numCalls = callUris.length; - PhoneCallDetails[] details = new PhoneCallDetails[numCalls]; - try { - for (int index = 0; index < numCalls; ++index) { - details[index] = getPhoneCallDetailsForUri(context, callUris[index]); - } - return details; - } catch (IllegalArgumentException e) { - // Something went wrong reading in our primary data. - LogUtil.e( - "CallLogAsyncTaskUtil.getCallDetails", "invalid URI starting call details", e); - return null; - } - } - - @Override - public void onPostExecute(PhoneCallDetails[] phoneCallDetails) { - if (callLogAsyncTaskListener != null) { - callLogAsyncTaskListener.onGetCallDetails(phoneCallDetails); - } - } - }); - } - - /** Return the phone call details for a given call log URI. */ - private static PhoneCallDetails getPhoneCallDetailsForUri( - @NonNull Context context, @NonNull Uri callUri) { - Cursor cursor = - context - .getContentResolver() - .query(callUri, CallDetailQuery.CALL_LOG_PROJECTION, null, null, null); - - try { - if (cursor == null || !cursor.moveToFirst()) { - throw new IllegalArgumentException("Cannot find content: " + callUri); - } - - // Read call log. - final String countryIso = cursor.getString(CallDetailQuery.COUNTRY_ISO_COLUMN_INDEX); - final String number = cursor.getString(CallDetailQuery.NUMBER_COLUMN_INDEX); - final String postDialDigits = - (VERSION.SDK_INT >= VERSION_CODES.N) - ? cursor.getString(CallDetailQuery.POST_DIAL_DIGITS) - : ""; - final String viaNumber = - (VERSION.SDK_INT >= VERSION_CODES.N) ? cursor.getString(CallDetailQuery.VIA_NUMBER) : ""; - final int numberPresentation = - cursor.getInt(CallDetailQuery.NUMBER_PRESENTATION_COLUMN_INDEX); - - final PhoneAccountHandle accountHandle = - PhoneAccountUtils.getAccount( - cursor.getString(CallDetailQuery.ACCOUNT_COMPONENT_NAME), - cursor.getString(CallDetailQuery.ACCOUNT_ID)); - - // If this is not a regular number, there is no point in looking it up in the contacts. - ContactInfoHelper contactInfoHelper = - new ContactInfoHelper(context, GeoUtil.getCurrentCountryIso(context)); - boolean isVoicemail = PhoneNumberHelper.isVoicemailNumber(context, accountHandle, number); - boolean shouldLookupNumber = - PhoneNumberHelper.canPlaceCallsTo(number, numberPresentation) && !isVoicemail; - ContactInfo info = ContactInfo.EMPTY; - - if (shouldLookupNumber) { - ContactInfo lookupInfo = contactInfoHelper.lookupNumber(number, countryIso); - info = lookupInfo != null ? lookupInfo : ContactInfo.EMPTY; - } - - PhoneCallDetails details = new PhoneCallDetails(number, numberPresentation, postDialDigits); - details.updateDisplayNumber(context, info.formattedNumber, isVoicemail); - - details.viaNumber = viaNumber; - details.accountHandle = accountHandle; - details.contactUri = info.lookupUri; - details.namePrimary = info.name; - details.nameAlternative = info.nameAlternative; - details.numberType = info.type; - details.numberLabel = info.label; - details.photoUri = info.photoUri; - details.sourceType = info.sourceType; - details.objectId = info.objectId; - - details.callTypes = new int[] {cursor.getInt(CallDetailQuery.CALL_TYPE_COLUMN_INDEX)}; - details.date = cursor.getLong(CallDetailQuery.DATE_COLUMN_INDEX); - details.duration = cursor.getLong(CallDetailQuery.DURATION_COLUMN_INDEX); - details.features = cursor.getInt(CallDetailQuery.FEATURES); - details.geocode = cursor.getString(CallDetailQuery.GEOCODED_LOCATION_COLUMN_INDEX); - details.transcription = cursor.getString(CallDetailQuery.TRANSCRIPTION_COLUMN_INDEX); - - details.countryIso = - !TextUtils.isEmpty(countryIso) ? countryIso : GeoUtil.getCurrentCountryIso(context); - - if (!cursor.isNull(CallDetailQuery.DATA_USAGE)) { - details.dataUsage = cursor.getLong(CallDetailQuery.DATA_USAGE); - } - - return details; - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - - /** - * Delete specified calls from the call log. - * - * @param context The context. - * @param callIds String of the callIds to delete from the call log, delimited by commas (","). - * @param callLogAsyncTaskListener The listener to invoke after the entries have been deleted. - */ - public static void deleteCalls( - @NonNull final Context context, - final String callIds, - @Nullable final CallLogAsyncTaskListener callLogAsyncTaskListener) { - if (sAsyncTaskExecutor == null) { - initTaskExecutor(); - } - - sAsyncTaskExecutor.submit( - Tasks.DELETE_CALL, - new AsyncTask<Void, Void, Void>() { - @Override - public Void doInBackground(Void... params) { - context - .getContentResolver() - .delete( - TelecomUtil.getCallLogUri(context), - CallLog.Calls._ID + " IN (" + callIds + ")", - null); - return null; - } - - @Override - public void onPostExecute(Void result) { - if (callLogAsyncTaskListener != null) { - callLogAsyncTaskListener.onDeleteCall(); - } - } - }); - } - public static void markVoicemailAsRead( @NonNull final Context context, @NonNull final Uri voicemailUri) { if (sAsyncTaskExecutor == null) { @@ -235,6 +60,8 @@ public class CallLogAsyncTaskUtil { .getContentResolver() .update(voicemailUri, values, Voicemails.IS_READ + " = 0", null); + uploadVoicemailLocalChangesToServer(context); + Intent intent = new Intent(context, CallLogNotificationsService.class); intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_VOICEMAILS_AS_OLD); context.startService(intent); @@ -256,7 +83,12 @@ public class CallLogAsyncTaskUtil { new AsyncTask<Void, Void, Void>() { @Override public Void doInBackground(Void... params) { - context.getContentResolver().delete(voicemailUri, null, null); + ContentValues values = new ContentValues(); + values.put(Voicemails.DELETED, "1"); + context.getContentResolver().update(voicemailUri, values, null, null); + // TODO(b/35440541): check which source package is changed. Don't need + // to upload changes on foreign voicemails, they will get a PROVIDER_CHANGED + uploadVoicemailLocalChangesToServer(context); return null; } @@ -305,11 +137,6 @@ public class CallLogAsyncTaskUtil { }); } - @VisibleForTesting - public static void resetForTest() { - sAsyncTaskExecutor = null; - } - /** The enumeration of {@link AsyncTask} objects used in this class. */ public enum Tasks { DELETE_VOICEMAIL, @@ -321,56 +148,12 @@ public class CallLogAsyncTaskUtil { } public interface CallLogAsyncTaskListener { - - void onDeleteCall(); - void onDeleteVoicemail(); - - void onGetCallDetails(PhoneCallDetails[] details); } - private static final class CallDetailQuery { - - public static final String[] CALL_LOG_PROJECTION; - static final int DATE_COLUMN_INDEX = 0; - static final int DURATION_COLUMN_INDEX = 1; - static final int NUMBER_COLUMN_INDEX = 2; - static final int CALL_TYPE_COLUMN_INDEX = 3; - static final int COUNTRY_ISO_COLUMN_INDEX = 4; - static final int GEOCODED_LOCATION_COLUMN_INDEX = 5; - static final int NUMBER_PRESENTATION_COLUMN_INDEX = 6; - static final int ACCOUNT_COMPONENT_NAME = 7; - static final int ACCOUNT_ID = 8; - static final int FEATURES = 9; - static final int DATA_USAGE = 10; - static final int TRANSCRIPTION_COLUMN_INDEX = 11; - static final int POST_DIAL_DIGITS = 12; - static final int VIA_NUMBER = 13; - private static final String[] CALL_LOG_PROJECTION_INTERNAL = - new String[] { - CallLog.Calls.DATE, - CallLog.Calls.DURATION, - CallLog.Calls.NUMBER, - CallLog.Calls.TYPE, - CallLog.Calls.COUNTRY_ISO, - CallLog.Calls.GEOCODED_LOCATION, - CallLog.Calls.NUMBER_PRESENTATION, - CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME, - CallLog.Calls.PHONE_ACCOUNT_ID, - CallLog.Calls.FEATURES, - CallLog.Calls.DATA_USAGE, - CallLog.Calls.TRANSCRIPTION - }; - - static { - ArrayList<String> projectionList = new ArrayList<>(); - projectionList.addAll(Arrays.asList(CALL_LOG_PROJECTION_INTERNAL)); - if (VERSION.SDK_INT >= VERSION_CODES.N) { - projectionList.add(CallLog.Calls.POST_DIAL_DIGITS); - projectionList.add(CallLog.Calls.VIA_NUMBER); - } - projectionList.trimToSize(); - CALL_LOG_PROJECTION = projectionList.toArray(new String[projectionList.size()]); - } + private static void uploadVoicemailLocalChangesToServer(Context context) { + Intent intent = new Intent(VoicemailClient.ACTION_UPLOAD); + intent.setPackage(context.getPackageName()); + context.sendBroadcast(intent); } } diff --git a/java/com/android/dialer/app/calllog/CallLogFragment.java b/java/com/android/dialer/app/calllog/CallLogFragment.java index 1ae68cd65..4abef3430 100644 --- a/java/com/android/dialer/app/calllog/CallLogFragment.java +++ b/java/com/android/dialer/app/calllog/CallLogFragment.java @@ -53,6 +53,7 @@ import com.android.dialer.app.list.ListsFragment.ListsPage; import com.android.dialer.app.voicemail.VoicemailPlaybackPresenter; import com.android.dialer.app.widget.EmptyContentView; import com.android.dialer.app.widget.EmptyContentView.OnEmptyViewActionButtonClickedListener; +import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler; import com.android.dialer.common.LogUtil; import com.android.dialer.database.CallLogQueryHandler; import com.android.dialer.phonenumbercache.ContactInfoHelper; @@ -70,9 +71,17 @@ public class CallLogFragment extends Fragment FragmentCompat.OnRequestPermissionsResultCallback, CallLogModalAlertManager.Listener { private static final String KEY_FILTER_TYPE = "filter_type"; + private static final String KEY_LOG_LIMIT = "log_limit"; + private static final String KEY_DATE_LIMIT = "date_limit"; + private static final String KEY_IS_CALL_LOG_ACTIVITY = "is_call_log_activity"; private static final String KEY_HAS_READ_CALL_LOG_PERMISSION = "has_read_call_log_permission"; private static final String KEY_REFRESH_DATA_REQUIRED = "refresh_data_required"; + // No limit specified for the number of logs to show; use the CallLogQueryHandler's default. + private static final int NO_LOG_LIMIT = -1; + // No date-based filtering. + private static final int NO_DATE_LIMIT = 0; + private static final int READ_CALL_LOG_PERMISSION_REQUEST_CODE = 1; private static final int EVENT_UPDATE_DISPLAY = 1; @@ -104,8 +113,17 @@ public class CallLogFragment extends Fragment // Exactly same variable is in Fragment as a package private. private boolean mMenuVisible = true; // Default to all calls. - protected int mCallTypeFilter = CallLogQueryHandler.CALL_TYPE_ALL; - + private int mCallTypeFilter = CallLogQueryHandler.CALL_TYPE_ALL; + // Log limit - if no limit is specified, then the default in {@link CallLogQueryHandler} + // will be used. + private int mLogLimit = NO_LOG_LIMIT; + // Date limit (in millis since epoch) - when non-zero, only calls which occurred on or after + // 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 shown in the CallLogActivity. + */ + private boolean mIsCallLogActivity = false; private final Handler mDisplayUpdateHandler = new Handler() { @Override @@ -121,6 +139,48 @@ public class CallLogFragment extends Fragment protected CallLogModalAlertManager mModalAlertManager; private ViewGroup mModalAlertView; + public CallLogFragment() { + this(CallLogQueryHandler.CALL_TYPE_ALL, NO_LOG_LIMIT); + } + + public CallLogFragment(int filterType) { + this(filterType, NO_LOG_LIMIT); + } + + public CallLogFragment(int filterType, boolean isCallLogActivity) { + this(filterType, NO_LOG_LIMIT); + mIsCallLogActivity = isCallLogActivity; + } + + public CallLogFragment(int filterType, int logLimit) { + this(filterType, logLimit, NO_DATE_LIMIT); + } + + /** + * Creates a call log fragment, filtering to include only calls of the desired type, occurring + * after the specified date. + * + * @param filterType type of calls to include. + * @param dateLimit limits results to calls occurring on or after the specified date. + */ + public CallLogFragment(int filterType, long dateLimit) { + this(filterType, NO_LOG_LIMIT, dateLimit); + } + + /** + * Creates a call log fragment, filtering to include only calls of the desired type, occurring + * after the specified date. Also provides a means to limit the number of results returned. + * + * @param filterType type of calls to include. + * @param logLimit limits the number of results to return. + * @param dateLimit limits results to calls occurring on or after the specified date. + */ + public CallLogFragment(int filterType, int logLimit, long dateLimit) { + mCallTypeFilter = filterType; + mLogLimit = logLimit; + mDateLimit = dateLimit; + } + @Override public void onCreate(Bundle state) { LogUtil.d("CallLogFragment.onCreate", toString()); @@ -128,13 +188,16 @@ public class CallLogFragment extends Fragment mRefreshDataRequired = true; if (state != null) { mCallTypeFilter = state.getInt(KEY_FILTER_TYPE, mCallTypeFilter); + mLogLimit = state.getInt(KEY_LOG_LIMIT, mLogLimit); + mDateLimit = state.getLong(KEY_DATE_LIMIT, mDateLimit); + mIsCallLogActivity = state.getBoolean(KEY_IS_CALL_LOG_ACTIVITY, mIsCallLogActivity); mHasReadCallLogPermission = state.getBoolean(KEY_HAS_READ_CALL_LOG_PERMISSION, false); mRefreshDataRequired = state.getBoolean(KEY_REFRESH_DATA_REQUIRED, mRefreshDataRequired); } final Activity activity = getActivity(); final ContentResolver resolver = activity.getContentResolver(); - mCallLogQueryHandler = new CallLogQueryHandler(activity, resolver, this); + mCallLogQueryHandler = new CallLogQueryHandler(activity, resolver, this, mLogLimit); mKeyguardManager = (KeyguardManager) activity.getSystemService(Context.KEYGUARD_SERVICE); resolver.registerContentObserver(CallLog.CONTENT_URI, true, mCallLogObserver); resolver.registerContentObserver( @@ -226,7 +289,10 @@ public class CallLogFragment extends Fragment } protected void setupData() { - int activityType = CallLogAdapter.ACTIVITY_TYPE_DIALTACTS; + int activityType = + mIsCallLogActivity + ? CallLogAdapter.ACTIVITY_TYPE_CALL_LOG + : CallLogAdapter.ACTIVITY_TYPE_DIALTACTS; String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity()); mContactInfoCache = @@ -244,6 +310,7 @@ public class CallLogFragment extends Fragment CallLogCache.getCallLogCache(getActivity()), mContactInfoCache, getVoicemailPlaybackPresenter(), + new FilteredNumberAsyncQueryHandler(getActivity()), activityType); mRecyclerView.setAdapter(mAdapter); fetchCalls(); @@ -324,6 +391,9 @@ public class CallLogFragment extends Fragment public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(KEY_FILTER_TYPE, mCallTypeFilter); + outState.putInt(KEY_LOG_LIMIT, mLogLimit); + outState.putLong(KEY_DATE_LIMIT, mDateLimit); + outState.putBoolean(KEY_IS_CALL_LOG_ACTIVITY, mIsCallLogActivity); outState.putBoolean(KEY_HAS_READ_CALL_LOG_PERMISSION, mHasReadCallLogPermission); outState.putBoolean(KEY_REFRESH_DATA_REQUIRED, mRefreshDataRequired); @@ -334,8 +404,10 @@ public class CallLogFragment extends Fragment @Override public void fetchCalls() { - mCallLogQueryHandler.fetchCalls(mCallTypeFilter); - ((ListsFragment) getParentFragment()).updateTabUnreadCounts(); + mCallLogQueryHandler.fetchCalls(mCallTypeFilter, mDateLimit); + if (!mIsCallLogActivity) { + ((ListsFragment) getParentFragment()).updateTabUnreadCounts(); + } } private void updateEmptyMessage(int filterType) { @@ -366,7 +438,9 @@ public class CallLogFragment extends Fragment "Unexpected filter type in CallLogFragment: " + filterType); } mEmptyListView.setDescription(messageId); - if (filterType == CallLogQueryHandler.CALL_TYPE_ALL) { + if (mIsCallLogActivity) { + mEmptyListView.setActionLabel(EmptyContentView.NO_LABEL); + } else if (filterType == CallLogQueryHandler.CALL_TYPE_ALL) { mEmptyListView.setActionLabel(R.string.call_log_all_empty_action); } } @@ -420,7 +494,7 @@ public class CallLogFragment extends Fragment if (mKeyguardManager != null && !mKeyguardManager.inKeyguardRestrictedInputMode() && mCallTypeFilter == Calls.VOICEMAIL_TYPE) { - CallLogNotificationsHelper.updateVoicemailNotifications(getActivity()); + CallLogNotificationsQueryHelper.updateVoicemailNotifications(getActivity()); } } @@ -434,7 +508,8 @@ public class CallLogFragment extends Fragment if (!PermissionsUtil.hasPermission(activity, READ_CALL_LOG)) { FragmentCompat.requestPermissions( this, new String[] {READ_CALL_LOG}, READ_CALL_LOG_PERMISSION_REQUEST_CODE); - } else { + } else if (!mIsCallLogActivity) { + // Show dialpad if we are not in the call log activity. ((HostInterface) activity).showDialpad(); } } diff --git a/java/com/android/dialer/app/calllog/CallLogListItemHelper.java b/java/com/android/dialer/app/calllog/CallLogListItemHelper.java index ea2119c83..a5df8cca1 100644 --- a/java/com/android/dialer/app/calllog/CallLogListItemHelper.java +++ b/java/com/android/dialer/app/calllog/CallLogListItemHelper.java @@ -21,18 +21,16 @@ import android.provider.CallLog.Calls; import android.support.annotation.WorkerThread; import android.text.SpannableStringBuilder; import android.text.TextUtils; -import android.util.Log; -import com.android.dialer.app.PhoneCallDetails; import com.android.dialer.app.R; import com.android.dialer.app.calllog.calllogcache.CallLogCache; +import com.android.dialer.calllogutils.PhoneCallDetails; import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; import com.android.dialer.compat.AppCompatConstants; /** Helper class to fill in the views of a call log entry. */ /* package */ class CallLogListItemHelper { - private static final String TAG = "CallLogListItemHelper"; - /** Helper for populating the details of a phone call. */ private final PhoneCallDetailsHelper mPhoneCallDetailsHelper; /** Resources to look up strings. */ @@ -105,7 +103,9 @@ import com.android.dialer.compat.AppCompatConstants; */ public void setActionContentDescriptions(CallLogListItemViewHolder views) { if (views.nameOrNumber == null) { - Log.e(TAG, "setActionContentDescriptions; name or number is null."); + LogUtil.e( + "CallLogListItemHelper.setActionContentDescriptions", + "setActionContentDescriptions; name or number is null."); } // Calling expandTemplate with a null parameter will cause a NullPointerException. @@ -170,7 +170,6 @@ import com.android.dialer.compat.AppCompatConstants; * * <p>2 calls. Answered call from John Doe mobile 1 hour ago. * - * @param context The application context. * @param details Details of call. * @return Return call action description. */ diff --git a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java index 6abd36078..8a2d94499 100644 --- a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java +++ b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java @@ -25,9 +25,11 @@ import android.os.AsyncTask; import android.provider.CallLog; import android.provider.CallLog.Calls; import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.support.annotation.VisibleForTesting; import android.support.v7.widget.CardView; import android.support.v7.widget.RecyclerView; import android.telecom.PhoneAccountHandle; +import android.telecom.TelecomManager; import android.telephony.PhoneNumberUtils; import android.text.BidiFormatter; import android.text.TextDirectionHeuristics; @@ -56,7 +58,7 @@ import com.android.dialer.blocking.FilteredNumberCompat; import com.android.dialer.blocking.FilteredNumbersUtil; import com.android.dialer.callcomposer.CallComposerActivity; import com.android.dialer.callcomposer.nano.CallComposerContact; -import com.android.dialer.common.ConfigProviderBindings; +import com.android.dialer.calldetails.nano.CallDetailsEntries; import com.android.dialer.common.LogUtil; import com.android.dialer.compat.CompatUtils; import com.android.dialer.logging.Logger; @@ -78,8 +80,6 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, MenuItem.OnMenuItemClickListener, View.OnCreateContextMenuListener { - private static final String CONFIG_SHARE_VOICEMAIL_ALLOWED = "share_voicemail_allowed"; - /** The root view of the call log list item */ public final View rootView; /** The quick contact badge for the contact. */ @@ -201,6 +201,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder public boolean isAttachedToWindow; public AsyncTask<Void, Void, ?> asyncTask; + private CallDetailsEntries callDetailsEntries; private CallLogListItemViewHolder( Context context, @@ -549,10 +550,6 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder } } - private static boolean isShareVoicemailAllowed(Context context) { - return ConfigProviderBindings.get(context).getBoolean(CONFIG_SHARE_VOICEMAIL_ALLOWED, true); - } - /** * Binds text titles, click handlers and intents to the voicemail, details and callback action * buttons. @@ -577,13 +574,14 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder unblockView.setVisibility(View.GONE); reportNotSpamView.setVisibility(View.GONE); - if (isShareVoicemailAllowed(mContext)) { - sendVoicemailButtonView.setVisibility(View.VISIBLE); - } voicemailPlaybackView.setVisibility(View.VISIBLE); Uri uri = Uri.parse(voicemailUri); mVoicemailPlaybackPresenter.setPlaybackView( - voicemailPlaybackView, rowId, uri, mVoicemailPrimaryActionButtonClicked); + voicemailPlaybackView, + rowId, + uri, + mVoicemailPrimaryActionButtonClicked, + sendVoicemailButtonView); mVoicemailPrimaryActionButtonClicked = false; CallLogAsyncTaskUtil.markVoicemailAsRead(mContext, uri); return; @@ -621,14 +619,14 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder && mVoicemailPlaybackPresenter != null && !TextUtils.isEmpty(voicemailUri)) { voicemailPlaybackView.setVisibility(View.VISIBLE); - if (isShareVoicemailAllowed(mContext)) { - Logger.get(mContext).logImpression(DialerImpression.Type.VVM_SHARE_VISIBLE); - sendVoicemailButtonView.setVisibility(View.VISIBLE); - } Uri uri = Uri.parse(voicemailUri); mVoicemailPlaybackPresenter.setPlaybackView( - voicemailPlaybackView, rowId, uri, mVoicemailPrimaryActionButtonClicked); + voicemailPlaybackView, + rowId, + uri, + mVoicemailPrimaryActionButtonClicked, + sendVoicemailButtonView); mVoicemailPrimaryActionButtonClicked = false; CallLogAsyncTaskUtil.markVoicemailAsRead(mContext, uri); } else { @@ -640,7 +638,8 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder detailsButtonView.setVisibility(View.GONE); } else { detailsButtonView.setVisibility(View.VISIBLE); - detailsButtonView.setTag(IntentProvider.getCallDetailIntentProvider(rowId, callIds, null)); + detailsButtonView.setTag( + IntentProvider.getCallDetailIntentProvider(callDetailsEntries, buildContact())); } boolean isBlockedOrSpam = blockId != null || (isSpamFeatureEnabled && isSpam); @@ -776,6 +775,8 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder contactType = ContactPhotoManager.TYPE_VOICEMAIL; } else if (isBusiness) { contactType = ContactPhotoManager.TYPE_BUSINESS; + } else if (numberPresentation == TelecomManager.PRESENTATION_RESTRICTED) { + contactType = ContactPhotoManager.TYPE_GENERIC_AVATAR; } final String lookupKey = @@ -854,20 +855,9 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder } else if (view.getId() == R.id.call_compose_action) { LogUtil.i("CallLogListItemViewHolder.onClick", "share and call pressed"); Logger.get(mContext).logImpression(DialerImpression.Type.CALL_LOG_SHARE_AND_CALL); - CallComposerContact contact = new CallComposerContact(); - contact.photoId = info.photoId; - contact.photoUri = info.photoUri == null ? null : info.photoUri.toString(); - contact.contactUri = info.lookupUri == null ? null : info.lookupUri.toString(); - contact.nameOrNumber = (String) nameOrNumber; - contact.isBusiness = isBusiness; - contact.number = number; - /* second line of contact view. */ - contact.displayNumber = TextUtils.isEmpty(info.name) ? null : displayNumber; - /* phone number type (e.g. mobile) in second line of contact view */ - contact.numberLabel = numberType; Activity activity = (Activity) mContext; activity.startActivityForResult( - CallComposerActivity.newIntent(activity, contact), + CallComposerActivity.newIntent(activity, buildContact()), DialtactsActivity.ACTIVITY_REQUEST_CODE_CALL_COMPOSE); } else if (view.getId() == R.id.share_voicemail) { Logger.get(mContext).logImpression(DialerImpression.Type.VVM_SHARE_PRESSED); @@ -885,6 +875,21 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder } } + private CallComposerContact buildContact() { + CallComposerContact contact = new CallComposerContact(); + contact.photoId = info.photoId; + contact.photoUri = info.photoUri == null ? null : info.photoUri.toString(); + contact.contactUri = info.lookupUri == null ? null : info.lookupUri.toString(); + contact.nameOrNumber = (String) nameOrNumber; + contact.isBusiness = isBusiness; + contact.number = number; + /* second line of contact view. */ + contact.displayNumber = TextUtils.isEmpty(info.name) ? null : displayNumber; + /* phone number type (e.g. mobile) in second line of contact view */ + contact.numberLabel = numberType; + return contact; + } + private void logCallLogAction(int id) { if (id == R.id.send_message_action) { Logger.get(mContext).logImpression(DialerImpression.Type.CALL_LOG_SEND_MESSAGE); @@ -931,6 +936,15 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder } } + public void setDetailedPhoneDetails(CallDetailsEntries callDetailsEntries) { + this.callDetailsEntries = callDetailsEntries; + } + + @VisibleForTesting + public CallDetailsEntries getDetailedPhoneDetails() { + return callDetailsEntries; + } + public interface OnClickListener { void onBlockReportSpam( diff --git a/java/com/android/dialer/app/calllog/CallLogNotificationsHelper.java b/java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java index 8f664d1a4..f12837e6f 100644 --- a/java/com/android/dialer/app/calllog/CallLogNotificationsHelper.java +++ b/java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java @@ -18,37 +18,41 @@ package com.android.dialer.app.calllog; import android.Manifest; import android.annotation.TargetApi; +import android.app.NotificationManager; import android.content.ContentResolver; import android.content.ContentUris; +import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.Build.VERSION_CODES; import android.provider.CallLog.Calls; import android.support.annotation.Nullable; +import android.support.annotation.WorkerThread; +import android.support.v4.os.UserManagerCompat; import android.telephony.PhoneNumberUtils; import android.text.TextUtils; -import android.util.Log; import com.android.contacts.common.GeoUtil; import com.android.dialer.app.R; +import com.android.dialer.calllogutils.PhoneNumberDisplayUtil; +import com.android.dialer.common.LogUtil; +import com.android.dialer.notification.GroupedNotificationUtil; import com.android.dialer.phonenumbercache.ContactInfo; import com.android.dialer.phonenumbercache.ContactInfoHelper; -import com.android.dialer.telecom.TelecomUtil; import com.android.dialer.util.PermissionsUtil; import java.util.ArrayList; import java.util.List; /** Helper class operating on call log notifications. */ -public class CallLogNotificationsHelper { +public class CallLogNotificationsQueryHelper { private static final String TAG = "CallLogNotifHelper"; - private static CallLogNotificationsHelper sInstance; private final Context mContext; private final NewCallsQuery mNewCallsQuery; private final ContactInfoHelper mContactInfoHelper; private final String mCurrentCountryIso; - CallLogNotificationsHelper( + CallLogNotificationsQueryHelper( Context context, NewCallsQuery newCallsQuery, ContactInfoHelper contactInfoHelper, @@ -59,29 +63,60 @@ public class CallLogNotificationsHelper { mCurrentCountryIso = countryIso; } - /** Returns the singleton instance of the {@link CallLogNotificationsHelper}. */ - public static CallLogNotificationsHelper getInstance(Context context) { - if (sInstance == null) { - ContentResolver contentResolver = context.getContentResolver(); - String countryIso = GeoUtil.getCurrentCountryIso(context); - sInstance = - new CallLogNotificationsHelper( - context, - createNewCallsQuery(context, contentResolver), - new ContactInfoHelper(context, countryIso), - countryIso); - } - return sInstance; + /** Returns an instance of {@link CallLogNotificationsQueryHelper}. */ + public static CallLogNotificationsQueryHelper getInstance(Context context) { + ContentResolver contentResolver = context.getContentResolver(); + String countryIso = GeoUtil.getCurrentCountryIso(context); + return new CallLogNotificationsQueryHelper( + context, + createNewCallsQuery(context, contentResolver), + new ContactInfoHelper(context, countryIso), + countryIso); } - /** Removes the missed call notifications. */ - public static void removeMissedCallNotifications(Context context) { - TelecomUtil.cancelMissedCallsNotification(context); + /** + * Removes the missed call notifications and marks calls as read. If a callUri is provided, only + * that call is marked as read. + */ + @WorkerThread + public static void removeMissedCallNotifications(Context context, @Nullable Uri callUri) { + // Call log is only accessible when unlocked. If that's the case, clear the list of + // new missed calls from the call log. + if (UserManagerCompat.isUserUnlocked(context) && PermissionsUtil.hasPhonePermissions(context)) { + ContentValues values = new ContentValues(); + values.put(Calls.NEW, 0); + values.put(Calls.IS_READ, 1); + StringBuilder where = new StringBuilder(); + where.append(Calls.NEW); + where.append(" = 1 AND "); + where.append(Calls.TYPE); + where.append(" = ?"); + try { + context + .getContentResolver() + .update( + callUri == null ? Calls.CONTENT_URI : callUri, + values, + where.toString(), + new String[] {Integer.toString(Calls.MISSED_TYPE)}); + } catch (IllegalArgumentException e) { + LogUtil.e( + "CallLogNotificationsQueryHelper.removeMissedCallNotifications", + "contacts provider update command failed", + e); + } + } + + GroupedNotificationUtil.removeNotification( + context.getSystemService(NotificationManager.class), + callUri != null ? callUri.toString() : null, + R.id.notification_missed_call, + MissedCallNotifier.NOTIFICATION_TAG); } /** Update the voice mail notifications. */ public static void updateVoicemailNotifications(Context context) { - CallLogNotificationsService.updateVoicemailNotifications(context, null); + CallLogNotificationsService.updateVoicemailNotifications(context); } /** Create a new instance of {@link NewCallsQuery}. */ @@ -251,7 +286,7 @@ public class CallLogNotificationsHelper { @TargetApi(VERSION_CODES.M) public List<NewCall> query(int type) { if (!PermissionsUtil.hasPermission(mContext, Manifest.permission.READ_CALL_LOG)) { - Log.w(TAG, "No READ_CALL_LOG permission, returning null for calls lookup."); + LogUtil.w(TAG, "No READ_CALL_LOG permission, returning null for calls lookup."); return null; } final String selection = String.format("%s = 1 AND %s = ?", Calls.NEW, Calls.TYPE); @@ -272,7 +307,7 @@ public class CallLogNotificationsHelper { } return newCalls; } catch (RuntimeException e) { - Log.w(TAG, "Exception when querying Contacts Provider for calls lookup"); + LogUtil.w(TAG, "Exception when querying Contacts Provider for calls lookup"); return null; } } diff --git a/java/com/android/dialer/app/calllog/CallLogNotificationsService.java b/java/com/android/dialer/app/calllog/CallLogNotificationsService.java index 820528126..b0d48eee5 100644 --- a/java/com/android/dialer/app/calllog/CallLogNotificationsService.java +++ b/java/com/android/dialer/app/calllog/CallLogNotificationsService.java @@ -20,6 +20,7 @@ import android.app.IntentService; import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.support.annotation.Nullable; import com.android.dialer.common.LogUtil; import com.android.dialer.telecom.TelecomUtil; import com.android.dialer.util.PermissionsUtil; @@ -44,21 +45,10 @@ public class CallLogNotificationsService extends IntentService { /** Action to mark all the new voicemails as old. */ public static final String ACTION_MARK_NEW_VOICEMAILS_AS_OLD = "com.android.dialer.calllog.ACTION_MARK_NEW_VOICEMAILS_AS_OLD"; - /** - * Action to update voicemail notifications. - * - * <p>May include an optional extra {@link #EXTRA_NEW_VOICEMAIL_URI}. - */ + /** Action to update voicemail notifications. */ public static final String ACTION_UPDATE_VOICEMAIL_NOTIFICATIONS = "com.android.dialer.calllog.UPDATE_VOICEMAIL_NOTIFICATIONS"; /** - * Extra to included with {@link #ACTION_UPDATE_VOICEMAIL_NOTIFICATIONS} to identify the new - * voicemail that triggered an update. - * - * <p>It must be a {@link Uri}. - */ - public static final String EXTRA_NEW_VOICEMAIL_URI = "NEW_VOICEMAIL_URI"; - /** * Action to update the missed call notifications. * * <p>Includes optional extras {@link #EXTRA_MISSED_CALL_NUMBER} and {@link @@ -66,9 +56,15 @@ public class CallLogNotificationsService extends IntentService { */ public static final String ACTION_UPDATE_MISSED_CALL_NOTIFICATIONS = "com.android.dialer.calllog.UPDATE_MISSED_CALL_NOTIFICATIONS"; + /** Action to mark all the new missed calls as old. */ public static final String ACTION_MARK_NEW_MISSED_CALLS_AS_OLD = "com.android.dialer.calllog.ACTION_MARK_NEW_MISSED_CALLS_AS_OLD"; + + /** Action to update missed call notifications with a post call note. */ + public static final String ACTION_INCOMING_POST_CALL = + "com.android.dialer.calllog.INCOMING_POST_CALL"; + /** Action to call back a missed call. */ public static final String ACTION_CALL_BACK_FROM_MISSED_CALL_NOTIFICATION = "com.android.dialer.calllog.CALL_BACK_FROM_MISSED_CALL_NOTIFICATION"; @@ -92,6 +88,21 @@ public class CallLogNotificationsService extends IntentService { */ public static final String EXTRA_MISSED_CALL_COUNT = "MISSED_CALL_COUNT"; + /** + * Extra to be included with {@link #ACTION_INCOMING_POST_CALL} to represent a post call note. + * + * <p>It must be a {@link String} + */ + public static final String EXTRA_POST_CALL_NOTE = "POST_CALL_NOTE"; + + /** + * Extra to be included with {@link #ACTION_INCOMING_POST_CALL} to represent the phone number the + * post call note came from. + * + * <p>It must be a {@link String} + */ + public static final String EXTRA_POST_CALL_NUMBER = "POST_CALL_NUMBER"; + public static final int UNKNOWN_MISSED_CALL_COUNT = -1; private VoicemailQueryHandler mVoicemailQueryHandler; @@ -103,10 +114,8 @@ public class CallLogNotificationsService extends IntentService { * Updates notifications for any new voicemails. * * @param context a valid context. - * @param voicemailUri The uri pointing to the voicemail to update the notification for. If {@code - * null}, then notifications for all new voicemails will be updated. */ - public static void updateVoicemailNotifications(Context context, Uri voicemailUri) { + public static void updateVoicemailNotifications(Context context) { if (!TelecomUtil.isDefaultDialer(context)) { LogUtil.i( "CallLogNotificationsService.updateVoicemailNotifications", @@ -116,10 +125,6 @@ public class CallLogNotificationsService extends IntentService { if (TelecomUtil.hasReadWriteVoicemailPermissions(context)) { Intent serviceIntent = new Intent(context, CallLogNotificationsService.class); serviceIntent.setAction(CallLogNotificationsService.ACTION_UPDATE_VOICEMAIL_NOTIFICATIONS); - // If voicemailUri is null, then notifications for all voicemails will be updated. - if (voicemailUri != null) { - serviceIntent.putExtra(CallLogNotificationsService.EXTRA_NEW_VOICEMAIL_URI, voicemailUri); - } context.startService(serviceIntent); } } @@ -139,9 +144,25 @@ public class CallLogNotificationsService extends IntentService { context.startService(serviceIntent); } - public static void markNewVoicemailsAsOld(Context context) { + public static void insertPostCallNote(Context context, String number, String postCallNote) { + Intent serviceIntent = new Intent(context, CallLogNotificationsService.class); + serviceIntent.setAction(ACTION_INCOMING_POST_CALL); + serviceIntent.putExtra(EXTRA_POST_CALL_NUMBER, number); + serviceIntent.putExtra(EXTRA_POST_CALL_NOTE, postCallNote); + context.startService(serviceIntent); + } + + public static void markNewVoicemailsAsOld(Context context, @Nullable Uri voicemailUri) { Intent serviceIntent = new Intent(context, CallLogNotificationsService.class); serviceIntent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_VOICEMAILS_AS_OLD); + serviceIntent.setData(voicemailUri); + context.startService(serviceIntent); + } + + public static void markNewMissedCallsAsOld(Context context, @Nullable Uri callUri) { + Intent serviceIntent = new Intent(context, CallLogNotificationsService.class); + serviceIntent.setAction(ACTION_MARK_NEW_MISSED_CALLS_AS_OLD); + serviceIntent.setData(callUri); context.startService(serviceIntent); } @@ -172,11 +193,10 @@ public class CallLogNotificationsService extends IntentService { if (mVoicemailQueryHandler == null) { mVoicemailQueryHandler = new VoicemailQueryHandler(this, getContentResolver()); } - mVoicemailQueryHandler.markNewVoicemailsAsOld(); + mVoicemailQueryHandler.markNewVoicemailsAsOld(intent.getData()); break; case ACTION_UPDATE_VOICEMAIL_NOTIFICATIONS: - Uri voicemailUri = intent.getParcelableExtra(EXTRA_NEW_VOICEMAIL_URI); - DefaultVoicemailNotifier.getInstance(this).updateNotification(voicemailUri); + DefaultVoicemailNotifier.getInstance(this).updateNotification(); break; case ACTION_UPDATE_MISSED_CALL_NOTIFICATIONS: int count = intent.getIntExtra(EXTRA_MISSED_CALL_COUNT, UNKNOWN_MISSED_CALL_COUNT); @@ -184,16 +204,24 @@ public class CallLogNotificationsService extends IntentService { MissedCallNotifier.getInstance(this).updateMissedCallNotification(count, number); updateBadgeCount(this, count); break; + case ACTION_INCOMING_POST_CALL: + String note = intent.getStringExtra(EXTRA_POST_CALL_NOTE); + String phoneNumber = intent.getStringExtra(EXTRA_POST_CALL_NUMBER); + MissedCallNotifier.getInstance(this).insertPostCallNotification(phoneNumber, note); + break; case ACTION_MARK_NEW_MISSED_CALLS_AS_OLD: - CallLogNotificationsHelper.removeMissedCallNotifications(this); + CallLogNotificationsQueryHelper.removeMissedCallNotifications(this, intent.getData()); + TelecomUtil.cancelMissedCallsNotification(this); break; case ACTION_CALL_BACK_FROM_MISSED_CALL_NOTIFICATION: MissedCallNotifier.getInstance(this) - .callBackFromMissedCall(intent.getStringExtra(EXTRA_MISSED_CALL_NUMBER)); + .callBackFromMissedCall( + intent.getStringExtra(EXTRA_MISSED_CALL_NUMBER), intent.getData()); break; case ACTION_SEND_SMS_FROM_MISSED_CALL_NOTIFICATION: MissedCallNotifier.getInstance(this) - .sendSmsFromMissedCall(intent.getStringExtra(EXTRA_MISSED_CALL_NUMBER)); + .sendSmsFromMissedCall( + intent.getStringExtra(EXTRA_MISSED_CALL_NUMBER), intent.getData()); break; default: LogUtil.d("CallLogNotificationsService.onHandleIntent", "could not handle: " + intent); diff --git a/java/com/android/dialer/app/calllog/CallLogReceiver.java b/java/com/android/dialer/app/calllog/CallLogReceiver.java index a781b0887..8fd1502bc 100644 --- a/java/com/android/dialer/app/calllog/CallLogReceiver.java +++ b/java/com/android/dialer/app/calllog/CallLogReceiver.java @@ -38,9 +38,9 @@ public class CallLogReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { if (VoicemailContract.ACTION_NEW_VOICEMAIL.equals(intent.getAction())) { checkVoicemailStatus(context); - CallLogNotificationsService.updateVoicemailNotifications(context, intent.getData()); + CallLogNotificationsService.updateVoicemailNotifications(context); } else if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { - CallLogNotificationsService.updateVoicemailNotifications(context, null); + CallLogNotificationsService.updateVoicemailNotifications(context); } else { LogUtil.w("CallLogReceiver.onReceive", "could not handle: " + intent); } diff --git a/java/com/android/dialer/app/calllog/CallTypeHelper.java b/java/com/android/dialer/app/calllog/CallTypeHelper.java deleted file mode 100644 index f3c27a1ac..000000000 --- a/java/com/android/dialer/app/calllog/CallTypeHelper.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2011 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.app.calllog; - -import android.content.res.Resources; -import com.android.dialer.app.R; -import com.android.dialer.compat.AppCompatConstants; - -/** Helper class to perform operations related to call types. */ -public class CallTypeHelper { - - /** Name used to identify incoming calls. */ - private final CharSequence mIncomingName; - /** Name used to identify incoming calls which were transferred to another device. */ - private final CharSequence mIncomingPulledName; - /** Name used to identify outgoing calls. */ - private final CharSequence mOutgoingName; - /** Name used to identify outgoing calls which were transferred to another device. */ - private final CharSequence mOutgoingPulledName; - /** Name used to identify missed calls. */ - private final CharSequence mMissedName; - /** Name used to identify incoming video calls. */ - private final CharSequence mIncomingVideoName; - /** Name used to identify incoming video calls which were transferred to another device. */ - private final CharSequence mIncomingVideoPulledName; - /** Name used to identify outgoing video calls. */ - private final CharSequence mOutgoingVideoName; - /** Name used to identify outgoing video calls which were transferred to another device. */ - private final CharSequence mOutgoingVideoPulledName; - /** Name used to identify missed video calls. */ - private final CharSequence mMissedVideoName; - /** Name used to identify voicemail calls. */ - private final CharSequence mVoicemailName; - /** Name used to identify rejected calls. */ - private final CharSequence mRejectedName; - /** Name used to identify blocked calls. */ - private final CharSequence mBlockedName; - /** Name used to identify calls which were answered on another device. */ - private final CharSequence mAnsweredElsewhereName; - - public CallTypeHelper(Resources resources) { - // Cache these values so that we do not need to look them up each time. - mIncomingName = resources.getString(R.string.type_incoming); - mIncomingPulledName = resources.getString(R.string.type_incoming_pulled); - mOutgoingName = resources.getString(R.string.type_outgoing); - mOutgoingPulledName = resources.getString(R.string.type_outgoing_pulled); - mMissedName = resources.getString(R.string.type_missed); - mIncomingVideoName = resources.getString(R.string.type_incoming_video); - mIncomingVideoPulledName = resources.getString(R.string.type_incoming_video_pulled); - mOutgoingVideoName = resources.getString(R.string.type_outgoing_video); - mOutgoingVideoPulledName = resources.getString(R.string.type_outgoing_video_pulled); - mMissedVideoName = resources.getString(R.string.type_missed_video); - mVoicemailName = resources.getString(R.string.type_voicemail); - mRejectedName = resources.getString(R.string.type_rejected); - mBlockedName = resources.getString(R.string.type_blocked); - mAnsweredElsewhereName = resources.getString(R.string.type_answered_elsewhere); - } - - public static boolean isMissedCallType(int callType) { - return (callType != AppCompatConstants.CALLS_INCOMING_TYPE - && callType != AppCompatConstants.CALLS_OUTGOING_TYPE - && callType != AppCompatConstants.CALLS_VOICEMAIL_TYPE - && callType != AppCompatConstants.CALLS_ANSWERED_EXTERNALLY_TYPE); - } - - /** Returns the text used to represent the given call type. */ - public CharSequence getCallTypeText(int callType, boolean isVideoCall, boolean isPulledCall) { - switch (callType) { - case AppCompatConstants.CALLS_INCOMING_TYPE: - if (isVideoCall) { - if (isPulledCall) { - return mIncomingVideoPulledName; - } else { - return mIncomingVideoName; - } - } else { - if (isPulledCall) { - return mIncomingPulledName; - } else { - return mIncomingName; - } - } - - case AppCompatConstants.CALLS_OUTGOING_TYPE: - if (isVideoCall) { - if (isPulledCall) { - return mOutgoingVideoPulledName; - } else { - return mOutgoingVideoName; - } - } else { - if (isPulledCall) { - return mOutgoingPulledName; - } else { - return mOutgoingName; - } - } - - case AppCompatConstants.CALLS_MISSED_TYPE: - if (isVideoCall) { - return mMissedVideoName; - } else { - return mMissedName; - } - - case AppCompatConstants.CALLS_VOICEMAIL_TYPE: - return mVoicemailName; - - case AppCompatConstants.CALLS_REJECTED_TYPE: - return mRejectedName; - - case AppCompatConstants.CALLS_BLOCKED_TYPE: - return mBlockedName; - - case AppCompatConstants.CALLS_ANSWERED_EXTERNALLY_TYPE: - return mAnsweredElsewhereName; - - default: - return mMissedName; - } - } -} diff --git a/java/com/android/dialer/app/calllog/CallTypeIconsView.java b/java/com/android/dialer/app/calllog/CallTypeIconsView.java deleted file mode 100644 index cd5c5460c..000000000 --- a/java/com/android/dialer/app/calllog/CallTypeIconsView.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright (C) 2011 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.app.calllog; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.PorterDuff; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.view.View; -import com.android.contacts.common.util.BitmapUtil; -import com.android.dialer.app.R; -import com.android.dialer.compat.AppCompatConstants; -import java.util.ArrayList; -import java.util.List; - -/** - * View that draws one or more symbols for different types of calls (missed calls, outgoing etc). - * The symbols are set up horizontally. As this view doesn't create subviews, it is better suited - * for ListView-recycling that a regular LinearLayout using ImageViews. - */ -public class CallTypeIconsView extends View { - - private static Resources sResources; - private List<Integer> mCallTypes = new ArrayList<>(3); - private boolean mShowVideo = false; - private int mWidth; - private int mHeight; - - public CallTypeIconsView(Context context) { - this(context, null); - } - - public CallTypeIconsView(Context context, AttributeSet attrs) { - super(context, attrs); - if (sResources == null) { - sResources = new Resources(context); - } - } - - public void clear() { - mCallTypes.clear(); - mWidth = 0; - mHeight = 0; - invalidate(); - } - - public void add(int callType) { - mCallTypes.add(callType); - - final Drawable drawable = getCallTypeDrawable(callType); - mWidth += drawable.getIntrinsicWidth() + sResources.iconMargin; - mHeight = Math.max(mHeight, drawable.getIntrinsicHeight()); - invalidate(); - } - - /** - * Determines whether the video call icon will be shown. - * - * @param showVideo True where the video icon should be shown. - */ - public void setShowVideo(boolean showVideo) { - mShowVideo = showVideo; - if (showVideo) { - mWidth += sResources.videoCall.getIntrinsicWidth(); - mHeight = Math.max(mHeight, sResources.videoCall.getIntrinsicHeight()); - invalidate(); - } - } - - /** - * Determines if the video icon should be shown. - * - * @return True if the video icon should be shown. - */ - public boolean isVideoShown() { - return mShowVideo; - } - - public int getCount() { - return mCallTypes.size(); - } - - public int getCallType(int index) { - return mCallTypes.get(index); - } - - private Drawable getCallTypeDrawable(int callType) { - switch (callType) { - case AppCompatConstants.CALLS_INCOMING_TYPE: - case AppCompatConstants.CALLS_ANSWERED_EXTERNALLY_TYPE: - return sResources.incoming; - case AppCompatConstants.CALLS_OUTGOING_TYPE: - return sResources.outgoing; - case AppCompatConstants.CALLS_MISSED_TYPE: - return sResources.missed; - case AppCompatConstants.CALLS_VOICEMAIL_TYPE: - return sResources.voicemail; - case AppCompatConstants.CALLS_BLOCKED_TYPE: - return sResources.blocked; - default: - // It is possible for users to end up with calls with unknown call types in their - // call history, possibly due to 3rd party call log implementations (e.g. to - // distinguish between rejected and missed calls). Instead of crashing, just - // assume that all unknown call types are missed calls. - return sResources.missed; - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - setMeasuredDimension(mWidth, mHeight); - } - - @Override - protected void onDraw(Canvas canvas) { - int left = 0; - for (Integer callType : mCallTypes) { - final Drawable drawable = getCallTypeDrawable(callType); - final int right = left + drawable.getIntrinsicWidth(); - drawable.setBounds(left, 0, right, drawable.getIntrinsicHeight()); - drawable.draw(canvas); - left = right + sResources.iconMargin; - } - - // If showing the video call icon, draw it scaled appropriately. - if (mShowVideo) { - final Drawable drawable = sResources.videoCall; - final int right = left + sResources.videoCall.getIntrinsicWidth(); - drawable.setBounds(left, 0, right, sResources.videoCall.getIntrinsicHeight()); - drawable.draw(canvas); - } - } - - private static class Resources { - - // Drawable representing an incoming answered call. - public final Drawable incoming; - - // Drawable respresenting an outgoing call. - public final Drawable outgoing; - - // Drawable representing an incoming missed call. - public final Drawable missed; - - // Drawable representing a voicemail. - public final Drawable voicemail; - - // Drawable representing a blocked call. - public final Drawable blocked; - - // Drawable repesenting a video call. - public final Drawable videoCall; - - /** The margin to use for icons. */ - public final int iconMargin; - - /** - * Configures the call icon drawables. A single white call arrow which points down and left is - * used as a basis for all of the call arrow icons, applying rotation and colors as needed. - * - * @param context The current context. - */ - public Resources(Context context) { - final android.content.res.Resources r = context.getResources(); - - incoming = r.getDrawable(R.drawable.ic_call_arrow); - incoming.setColorFilter(r.getColor(R.color.answered_call), PorterDuff.Mode.MULTIPLY); - - // Create a rotated instance of the call arrow for outgoing calls. - outgoing = BitmapUtil.getRotatedDrawable(r, R.drawable.ic_call_arrow, 180f); - outgoing.setColorFilter(r.getColor(R.color.answered_call), PorterDuff.Mode.MULTIPLY); - - // Need to make a copy of the arrow drawable, otherwise the same instance colored - // above will be recolored here. - missed = r.getDrawable(R.drawable.ic_call_arrow).mutate(); - missed.setColorFilter(r.getColor(R.color.missed_call), PorterDuff.Mode.MULTIPLY); - - voicemail = r.getDrawable(R.drawable.quantum_ic_voicemail_white_18); - voicemail.setColorFilter( - r.getColor(R.color.dialer_secondary_text_color), PorterDuff.Mode.MULTIPLY); - - blocked = getScaledBitmap(context, R.drawable.ic_block_24dp); - blocked.setColorFilter(r.getColor(R.color.blocked_call), PorterDuff.Mode.MULTIPLY); - - videoCall = getScaledBitmap(context, R.drawable.quantum_ic_videocam_white_24); - videoCall.setColorFilter( - r.getColor(R.color.dialer_secondary_text_color), PorterDuff.Mode.MULTIPLY); - - iconMargin = r.getDimensionPixelSize(R.dimen.call_log_icon_margin); - } - - // Gets the icon, scaled to the height of the call type icons. This helps display all the - // icons to be the same height, while preserving their width aspect ratio. - private Drawable getScaledBitmap(Context context, int resourceId) { - Bitmap icon = BitmapFactory.decodeResource(context.getResources(), resourceId); - int scaledHeight = context.getResources().getDimensionPixelSize(R.dimen.call_type_icon_size); - int scaledWidth = - (int) ((float) icon.getWidth() * ((float) scaledHeight / (float) icon.getHeight())); - Bitmap scaledIcon = Bitmap.createScaledBitmap(icon, scaledWidth, scaledHeight, false); - return new BitmapDrawable(context.getResources(), scaledIcon); - } - } -} diff --git a/java/com/android/dialer/app/calllog/DefaultVoicemailNotifier.java b/java/com/android/dialer/app/calllog/DefaultVoicemailNotifier.java index 651a0ccb8..cc1dc4f20 100644 --- a/java/com/android/dialer/app/calllog/DefaultVoicemailNotifier.java +++ b/java/com/android/dialer/app/calllog/DefaultVoicemailNotifier.java @@ -19,31 +19,33 @@ package com.android.dialer.app.calllog; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.content.res.Resources; +import android.graphics.Bitmap; import android.net.Uri; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; import android.support.v4.util.Pair; -import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArrayMap; -import android.util.Log; import com.android.contacts.common.compat.TelephonyManagerCompat; import com.android.contacts.common.util.ContactDisplayUtils; import com.android.dialer.app.DialtactsActivity; import com.android.dialer.app.R; -import com.android.dialer.app.calllog.CallLogNotificationsHelper.NewCall; +import com.android.dialer.app.calllog.CallLogNotificationsQueryHelper.NewCall; +import com.android.dialer.app.contactinfo.ContactPhotoLoader; import com.android.dialer.app.list.ListsFragment; import com.android.dialer.blocking.FilteredNumbersUtil; -import com.android.dialer.telecom.TelecomUtil; +import com.android.dialer.common.LogUtil; +import com.android.dialer.notification.NotificationChannelManager; +import com.android.dialer.notification.NotificationChannelManager.Channel; +import com.android.dialer.phonenumbercache.ContactInfo; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -54,26 +56,23 @@ public class DefaultVoicemailNotifier { public static final String TAG = "VoicemailNotifier"; /** The tag used to identify notifications from this class. */ - private static final String NOTIFICATION_TAG = "DefaultVoicemailNotifier"; + static final String NOTIFICATION_TAG = "DefaultVoicemailNotifier"; /** The identifier of the notification of new voicemails. */ - private static final int NOTIFICATION_ID = 1; + private static final int NOTIFICATION_ID = R.id.notification_voicemail; - /** The singleton instance of {@link DefaultVoicemailNotifier}. */ - private static DefaultVoicemailNotifier sInstance; + private final Context context; + private final CallLogNotificationsQueryHelper queryHelper; - private final Context mContext; - - private DefaultVoicemailNotifier(Context context) { - mContext = context; + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + DefaultVoicemailNotifier(Context context, CallLogNotificationsQueryHelper queryHelper) { + this.context = context; + this.queryHelper = queryHelper; } - /** Returns the singleton instance of the {@link DefaultVoicemailNotifier}. */ + /** Returns an instance of {@link DefaultVoicemailNotifier}. */ public static DefaultVoicemailNotifier getInstance(Context context) { - if (sInstance == null) { - ContentResolver contentResolver = context.getContentResolver(); - sInstance = new DefaultVoicemailNotifier(context); - } - return sInstance; + return new DefaultVoicemailNotifier( + context, CallLogNotificationsQueryHelper.getInstance(context)); } /** @@ -84,34 +83,23 @@ public class DefaultVoicemailNotifier { * * <p>It is not safe to call this method from the main thread. */ - public void updateNotification(Uri newCallUri) { + public void updateNotification() { // Lookup the list of new voicemails to include in the notification. - // TODO: Move this into a service, to avoid holding the receiver up. - final List<NewCall> newCalls = - CallLogNotificationsHelper.getInstance(mContext).getNewVoicemails(); + final List<NewCall> newCalls = queryHelper.getNewVoicemails(); if (newCalls == null) { // Query failed, just return. return; } - if (newCalls.isEmpty()) { - // No voicemails to notify about: clear the notification. - getNotificationManager().cancel(NOTIFICATION_TAG, NOTIFICATION_ID); - return; - } - - Resources resources = mContext.getResources(); + Resources resources = context.getResources(); // This represents a list of names to include in the notification. String callers = null; // Maps each number into a name: if a number is in the map, it has already left a more // recent voicemail. - final Map<String, String> names = new ArrayMap<>(); - - // Determine the call corresponding to the new voicemail we have to notify about. - NewCall callToNotify = null; + final Map<String, ContactInfo> contactInfos = new ArrayMap<>(); // Iterate over the new voicemails to determine all the information above. Iterator<NewCall> itr = newCalls.iterator(); @@ -120,95 +108,64 @@ public class DefaultVoicemailNotifier { // Skip notifying for numbers which are blocked. if (FilteredNumbersUtil.shouldBlockVoicemail( - mContext, newCall.number, newCall.countryIso, newCall.dateMs)) { + context, newCall.number, newCall.countryIso, newCall.dateMs)) { itr.remove(); // Delete the voicemail. - mContext.getContentResolver().delete(newCall.voicemailUri, null, null); + context.getContentResolver().delete(newCall.voicemailUri, null, null); continue; } // Check if we already know the name associated with this number. - String name = names.get(newCall.number); - if (name == null) { - name = - CallLogNotificationsHelper.getInstance(mContext) - .getName(newCall.number, newCall.numberPresentation, newCall.countryIso); - names.put(newCall.number, name); + ContactInfo contactInfo = contactInfos.get(newCall.number); + if (contactInfo == null) { + contactInfo = + queryHelper.getContactInfo( + newCall.number, newCall.numberPresentation, newCall.countryIso); + contactInfos.put(newCall.number, contactInfo); // This is a new caller. Add it to the back of the list of callers. if (TextUtils.isEmpty(callers)) { - callers = name; + callers = contactInfo.name; } else { callers = - resources.getString(R.string.notification_voicemail_callers_list, callers, name); + resources.getString( + R.string.notification_voicemail_callers_list, callers, contactInfo.name); } } - // Check if this is the new call we need to notify about. - if (newCallUri != null - && newCall.voicemailUri != null - && ContentUris.parseId(newCallUri) == ContentUris.parseId(newCall.voicemailUri)) { - callToNotify = newCall; - } } - // All the potential new voicemails have been removed, e.g. if they were spam. if (newCalls.isEmpty()) { + // No voicemails to notify about: clear the notification. + CallLogNotificationsService.markNewVoicemailsAsOld(context, null); return; } - // If there is only one voicemail, set its transcription as the "long text". - String transcription = null; - if (newCalls.size() == 1) { - transcription = newCalls.get(0).transcription; - } - - if (newCallUri != null && callToNotify == null) { - Log.e(TAG, "The new call could not be found in the call log: " + newCallUri); - } - - // Determine the title of the notification and the icon for it. - final String title = - resources.getQuantityString( - R.plurals.notification_voicemail_title, newCalls.size(), newCalls.size()); - // TODO: Use the photo of contact if all calls are from the same person. - final int icon = android.R.drawable.stat_notify_voicemail; - - Pair<Uri, Integer> info = getNotificationInfo(callToNotify); - - Notification.Builder notificationBuilder = - new Notification.Builder(mContext) - .setSmallIcon(icon) - .setContentTitle(title) + Notification.Builder groupSummary = + createNotificationBuilder() + .setContentTitle( + resources.getQuantityString( + R.plurals.notification_voicemail_title, newCalls.size(), newCalls.size())) .setContentText(callers) - .setColor(resources.getColor(R.color.dialer_theme_color)) - .setSound(info.first) - .setDefaults(info.second) - .setDeleteIntent(createMarkNewVoicemailsAsOldIntent()) - .setAutoCancel(true); - - if (!TextUtils.isEmpty(transcription)) { - notificationBuilder.setStyle(new Notification.BigTextStyle().bigText(transcription)); + .setDeleteIntent(createMarkNewVoicemailsAsOldIntent(null)) + .setGroupSummary(true) + .setContentIntent(newVoicemailIntent(null)); + + NotificationChannelManager.applyChannel( + groupSummary, + context, + Channel.VOICEMAIL, + PhoneAccountHandles.getAccount(context, newCalls.get(0))); + + LogUtil.i(TAG, "Creating voicemail notification"); + getNotificationManager().notify(NOTIFICATION_TAG, NOTIFICATION_ID, groupSummary.build()); + + for (NewCall voicemail : newCalls) { + getNotificationManager() + .notify( + voicemail.voicemailUri.toString(), + NOTIFICATION_ID, + createNotificationForVoicemail(voicemail, contactInfos)); } - - // Determine the intent to fire when the notification is clicked on. - final Intent contentIntent; - // Open the call log. - contentIntent = DialtactsActivity.getShowTabIntent(mContext, ListsFragment.TAB_INDEX_VOICEMAIL); - contentIntent.putExtra(DialtactsActivity.EXTRA_CLEAR_NEW_VOICEMAILS, true); - notificationBuilder.setContentIntent( - PendingIntent.getActivity(mContext, 0, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT)); - - // The text to show in the ticker, describing the new event. - if (callToNotify != null) { - CharSequence msg = - ContactDisplayUtils.getTtsSpannedPhoneNumber( - resources, - R.string.notification_new_voicemail_ticker, - names.get(callToNotify.number)); - notificationBuilder.setTicker(msg); - } - Log.i(TAG, "Creating voicemail notification"); - getNotificationManager().notify(NOTIFICATION_TAG, NOTIFICATION_ID, notificationBuilder.build()); } /** @@ -216,30 +173,15 @@ public class DefaultVoicemailNotifier { * for the given call. */ private Pair<Uri, Integer> getNotificationInfo(@Nullable NewCall callToNotify) { - Log.v(TAG, "getNotificationInfo"); + LogUtil.v(TAG, "getNotificationInfo"); if (callToNotify == null) { - Log.i(TAG, "callToNotify == null"); + LogUtil.i(TAG, "callToNotify == null"); return new Pair<>(null, 0); } - PhoneAccountHandle accountHandle; - if (callToNotify.accountComponentName == null || callToNotify.accountId == null) { - Log.v(TAG, "accountComponentName == null || callToNotify.accountId == null"); - accountHandle = TelecomUtil.getDefaultOutgoingPhoneAccount(mContext, PhoneAccount.SCHEME_TEL); - if (accountHandle == null) { - Log.i(TAG, "No default phone account found, using default notification ringtone"); - return new Pair<>(null, Notification.DEFAULT_ALL); - } - - } else { - accountHandle = - new PhoneAccountHandle( - ComponentName.unflattenFromString(callToNotify.accountComponentName), - callToNotify.accountId); - } - if (accountHandle.getComponentName() != null) { - Log.v(TAG, "PhoneAccountHandle.ComponentInfo:" + accountHandle.getComponentName()); - } else { - Log.i(TAG, "PhoneAccountHandle.ComponentInfo: null"); + PhoneAccountHandle accountHandle = PhoneAccountHandles.getAccount(context, callToNotify); + if (accountHandle == null) { + LogUtil.i(TAG, "No default phone account found, using default notification ringtone"); + return new Pair<>(null, Notification.DEFAULT_ALL); } return new Pair<>( TelephonyManagerCompat.getVoicemailRingtoneUri(getTelephonyManager(), accountHandle), @@ -257,17 +199,79 @@ public class DefaultVoicemailNotifier { } /** Creates a pending intent that marks all new voicemails as old. */ - private PendingIntent createMarkNewVoicemailsAsOldIntent() { - Intent intent = new Intent(mContext, CallLogNotificationsService.class); + private PendingIntent createMarkNewVoicemailsAsOldIntent(@Nullable Uri voicemailUri) { + Intent intent = new Intent(context, CallLogNotificationsService.class); intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_VOICEMAILS_AS_OLD); - return PendingIntent.getService(mContext, 0, intent, 0); + intent.setData(voicemailUri); + return PendingIntent.getService(context, 0, intent, 0); } private NotificationManager getNotificationManager() { - return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); } private TelephonyManager getTelephonyManager() { - return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + return (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + } + + private Notification createNotificationForVoicemail( + @NonNull NewCall voicemail, @NonNull Map<String, ContactInfo> contactInfos) { + Pair<Uri, Integer> notificationInfo = getNotificationInfo(voicemail); + ContactInfo contactInfo = contactInfos.get(voicemail.number); + + Notification.Builder notificationBuilder = + createNotificationBuilder() + .setContentTitle( + context + .getResources() + .getQuantityString(R.plurals.notification_voicemail_title, 1, 1)) + .setContentText( + ContactDisplayUtils.getTtsSpannedPhoneNumber( + context.getResources(), + R.string.notification_new_voicemail_ticker, + contactInfo.name)) + .setWhen(voicemail.dateMs) + .setSound(notificationInfo.first) + .setDefaults(notificationInfo.second) + .setDeleteIntent(createMarkNewVoicemailsAsOldIntent(voicemail.voicemailUri)); + + NotificationChannelManager.applyChannel( + notificationBuilder, + context, + Channel.VOICEMAIL, + PhoneAccountHandles.getAccount(context, voicemail)); + + ContactPhotoLoader loader = new ContactPhotoLoader(context, contactInfo); + Bitmap photoIcon = loader.loadPhotoIcon(); + if (photoIcon != null) { + notificationBuilder.setLargeIcon(photoIcon); + } + + if (!TextUtils.isEmpty(voicemail.transcription)) { + notificationBuilder.setStyle( + new Notification.BigTextStyle().bigText(voicemail.transcription)); + } + notificationBuilder.setContentIntent(newVoicemailIntent(voicemail)); + + return notificationBuilder.build(); + } + + private Notification.Builder createNotificationBuilder() { + return new Notification.Builder(context) + .setSmallIcon(android.R.drawable.stat_notify_voicemail) + .setColor(context.getColor(R.color.dialer_theme_color)) + .setGroup(NOTIFICATION_TAG) + .setOnlyAlertOnce(true) + .setAutoCancel(true); + } + + private PendingIntent newVoicemailIntent(@Nullable NewCall voicemail) { + Intent intent = DialtactsActivity.getShowTabIntent(context, ListsFragment.TAB_INDEX_VOICEMAIL); + // TODO (b/35486204): scroll to this voicemail + if (voicemail != null) { + intent.setData(voicemail.voicemailUri); + } + intent.putExtra(DialtactsActivity.EXTRA_CLEAR_NEW_VOICEMAILS, true); + return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); } } diff --git a/java/com/android/dialer/app/calllog/IntentProvider.java b/java/com/android/dialer/app/calllog/IntentProvider.java index 879ac353d..c53e3ec5e 100644 --- a/java/com/android/dialer/app/calllog/IntentProvider.java +++ b/java/com/android/dialer/app/calllog/IntentProvider.java @@ -16,7 +16,6 @@ package com.android.dialer.app.calllog; -import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.Intent; @@ -25,10 +24,11 @@ import android.provider.ContactsContract; import android.telecom.PhoneAccountHandle; import com.android.contacts.common.model.Contact; import com.android.contacts.common.model.ContactLoader; -import com.android.dialer.app.CallDetailActivity; +import com.android.dialer.callcomposer.nano.CallComposerContact; +import com.android.dialer.calldetails.CallDetailsActivity; +import com.android.dialer.calldetails.nano.CallDetailsEntries; import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.callintent.nano.CallInitiationType; -import com.android.dialer.telecom.TelecomUtil; import com.android.dialer.util.CallUtil; import com.android.dialer.util.IntentUtil; import java.util.ArrayList; @@ -97,29 +97,16 @@ public abstract class IntentProvider { /** * Retrieves the call details intent provider for an entry in the call log. * - * @param id The call ID of the first call in the call group. - * @param extraIds The call ID of the other calls grouped together with the call. - * @param voicemailUri If call log entry is for a voicemail, the voicemail URI. + * @param callDetailsEntries The call details of the other calls grouped together with the call. + * @param contact The contact with which this call details intent pertains to. * @return The call details intent provider. */ public static IntentProvider getCallDetailIntentProvider( - final long id, final long[] extraIds, final String voicemailUri) { + CallDetailsEntries callDetailsEntries, CallComposerContact contact) { return new IntentProvider() { @Override public Intent getIntent(Context context) { - Intent intent = new Intent(context, CallDetailActivity.class); - // Check if the first item is a voicemail. - if (voicemailUri != null) { - intent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI, Uri.parse(voicemailUri)); - } - - if (extraIds != null && extraIds.length > 0) { - intent.putExtra(CallDetailActivity.EXTRA_CALL_LOG_IDS, extraIds); - } else { - // If there is a single item, use the direct URI for it. - intent.setData(ContentUris.withAppendedId(TelecomUtil.getCallLogUri(context), id)); - } - return intent; + return CallDetailsActivity.newInstance(context, callDetailsEntries, contact); } }; } diff --git a/java/com/android/dialer/app/calllog/MissedCallNotifier.java b/java/com/android/dialer/app/calllog/MissedCallNotifier.java index 2fa3dae65..5b5661615 100644 --- a/java/com/android/dialer/app/calllog/MissedCallNotifier.java +++ b/java/com/android/dialer/app/calllog/MissedCallNotifier.java @@ -16,16 +16,20 @@ package com.android.dialer.app.calllog; import android.app.Notification; +import android.app.Notification.Builder; import android.app.NotificationManager; import android.app.PendingIntent; -import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; -import android.os.AsyncTask; +import android.graphics.drawable.Icon; +import android.net.Uri; import android.provider.CallLog.Calls; +import android.service.notification.StatusBarNotification; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; +import android.support.annotation.WorkerThread; import android.support.v4.os.UserManagerCompat; import android.text.BidiFormatter; import android.text.TextDirectionHeuristics; @@ -34,109 +38,117 @@ import com.android.contacts.common.ContactsUtils; import com.android.contacts.common.compat.PhoneNumberUtilsCompat; import com.android.dialer.app.DialtactsActivity; import com.android.dialer.app.R; -import com.android.dialer.app.calllog.CallLogNotificationsHelper.NewCall; +import com.android.dialer.app.calllog.CallLogNotificationsQueryHelper.NewCall; import com.android.dialer.app.contactinfo.ContactPhotoLoader; import com.android.dialer.app.list.ListsFragment; import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.callintent.nano.CallInitiationType; -import com.android.dialer.common.ConfigProviderBindings; import com.android.dialer.common.LogUtil; +import com.android.dialer.notification.NotificationChannelManager; +import com.android.dialer.notification.NotificationChannelManager.Channel; import com.android.dialer.phonenumbercache.ContactInfo; import com.android.dialer.phonenumberutil.PhoneNumberHelper; +import com.android.dialer.telecom.TelecomUtil; import com.android.dialer.util.DialerUtils; import com.android.dialer.util.IntentUtil; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** Creates a notification for calls that the user missed (neither answered nor rejected). */ public class MissedCallNotifier { /** The tag used to identify notifications from this class. */ - private static final String NOTIFICATION_TAG = "MissedCallNotifier"; + static final String NOTIFICATION_TAG = "MissedCallNotifier"; /** The identifier of the notification of new missed calls. */ - private static final int NOTIFICATION_ID = 1; + private static final int NOTIFICATION_ID = R.id.notification_missed_call; - private static MissedCallNotifier sInstance; - private Context mContext; - private CallLogNotificationsHelper mCalllogNotificationsHelper; + private final Context context; + private final CallLogNotificationsQueryHelper callLogNotificationsQueryHelper; @VisibleForTesting - MissedCallNotifier(Context context, CallLogNotificationsHelper callLogNotificationsHelper) { - mContext = context; - mCalllogNotificationsHelper = callLogNotificationsHelper; + MissedCallNotifier( + Context context, CallLogNotificationsQueryHelper callLogNotificationsQueryHelper) { + this.context = context; + this.callLogNotificationsQueryHelper = callLogNotificationsQueryHelper; } - /** Returns the singleton instance of the {@link MissedCallNotifier}. */ + /** Returns an instance of {@link MissedCallNotifier}. */ public static MissedCallNotifier getInstance(Context context) { - if (sInstance == null) { - CallLogNotificationsHelper callLogNotificationsHelper = - CallLogNotificationsHelper.getInstance(context); - sInstance = new MissedCallNotifier(context, callLogNotificationsHelper); - } - return sInstance; + CallLogNotificationsQueryHelper callLogNotificationsQueryHelper = + CallLogNotificationsQueryHelper.getInstance(context); + return new MissedCallNotifier(context, callLogNotificationsQueryHelper); } /** - * Creates a missed call notification with a post call message if there are no existing missed - * calls. + * Update missed call notifications from the call log. Accepts default information in case call + * log cannot be accessed. + * + * @param count the number of missed calls to display if call log cannot be accessed. May be + * {@link CallLogNotificationsService#UNKNOWN_MISSED_CALL_COUNT} if unknown. + * @param number the phone number of the most recent call to display if the call log cannot be + * accessed. May be null if unknown. */ - public void createPostCallMessageNotification(String number, String message) { - int count = CallLogNotificationsService.UNKNOWN_MISSED_CALL_COUNT; - if (ConfigProviderBindings.get(mContext).getBoolean("enable_call_compose", false)) { - updateMissedCallNotification(count, number, message); - } else { - updateMissedCallNotification(count, number, null); - } - } - - /** Creates a missed call notification. */ - public void updateMissedCallNotification(int count, String number) { - updateMissedCallNotification(count, number, null); - } - - private void updateMissedCallNotification( - int count, String number, @Nullable String postCallMessage) { + @WorkerThread + public void updateMissedCallNotification(int count, @Nullable String number) { final int titleResId; CharSequence expandedText; // The text in the notification's line 1 and 2. - final List<NewCall> newCalls = mCalllogNotificationsHelper.getNewMissedCalls(); + List<NewCall> newCalls = callLogNotificationsQueryHelper.getNewMissedCalls(); - if (count == CallLogNotificationsService.UNKNOWN_MISSED_CALL_COUNT) { - if (newCalls == null) { - // If the intent did not contain a count, and we are unable to get a count from the - // call log, then no notification can be shown. - return; + if ((newCalls != null && newCalls.isEmpty()) || count == 0) { + // No calls to notify about: clear the notification. + CallLogNotificationsQueryHelper.removeMissedCallNotifications(context, null); + return; + } + + if (newCalls != null) { + if (count != CallLogNotificationsService.UNKNOWN_MISSED_CALL_COUNT + && count != newCalls.size()) { + LogUtil.w( + "MissedCallNotifier.updateMissedCallNotification", + "Call count does not match call log count." + + " count: " + + count + + " newCalls.size(): " + + newCalls.size()); } count = newCalls.size(); } - if (count == 0) { - // No voicemails to notify about: clear the notification. - clearMissedCalls(); + if (count == CallLogNotificationsService.UNKNOWN_MISSED_CALL_COUNT) { + // If the intent did not contain a count, and we are unable to get a count from the + // call log, then no notification can be shown. return; } - // The call log has been updated, use that information preferentially. - boolean useCallLog = newCalls != null && newCalls.size() == count; - NewCall newestCall = useCallLog ? newCalls.get(0) : null; - long timeMs = useCallLog ? newestCall.dateMs : System.currentTimeMillis(); - String missedNumber = useCallLog ? newestCall.number : number; + Notification.Builder groupSummary = createNotificationBuilder(); + boolean useCallList = newCalls != null; - Notification.Builder builder = new Notification.Builder(mContext); - // Display the first line of the notification: - // 1 missed call: <caller name || handle> - // More than 1 missed call: <number of calls> + "missed calls" if (count == 1) { + NewCall call = + useCallList + ? newCalls.get(0) + : new NewCall( + null, + null, + number, + Calls.PRESENTATION_ALLOWED, + null, + null, + null, + null, + System.currentTimeMillis()); + //TODO: look up caller ID that is not in contacts. ContactInfo contactInfo = - mCalllogNotificationsHelper.getContactInfo( - missedNumber, - useCallLog ? newestCall.numberPresentation : Calls.PRESENTATION_ALLOWED, - useCallLog ? newestCall.countryIso : null); - + callLogNotificationsQueryHelper.getContactInfo( + call.number, call.numberPresentation, call.countryIso); titleResId = contactInfo.userType == ContactsUtils.USER_TYPE_WORK ? R.string.notification_missedWorkCallTitle : R.string.notification_missedCallTitle; + if (TextUtils.equals(contactInfo.name, contactInfo.formattedNumber) || TextUtils.equals(contactInfo.name, contactInfo.number)) { expandedText = @@ -147,134 +159,195 @@ public class MissedCallNotifier { expandedText = contactInfo.name; } - if (!TextUtils.isEmpty(postCallMessage)) { - // Ex. "John Doe: Hey dude" - expandedText = - mContext.getString( - R.string.post_call_notification_message, expandedText, postCallMessage); - } - ContactPhotoLoader loader = new ContactPhotoLoader(mContext, contactInfo); + ContactPhotoLoader loader = new ContactPhotoLoader(context, contactInfo); Bitmap photoIcon = loader.loadPhotoIcon(); if (photoIcon != null) { - builder.setLargeIcon(photoIcon); + groupSummary.setLargeIcon(photoIcon); } } else { titleResId = R.string.notification_missedCallsTitle; - expandedText = mContext.getString(R.string.notification_missedCallsMsg, count); + expandedText = context.getString(R.string.notification_missedCallsMsg, count); } // Create a public viewable version of the notification, suitable for display when sensitive // notification content is hidden. - Notification.Builder publicBuilder = new Notification.Builder(mContext); - publicBuilder - .setSmallIcon(android.R.drawable.stat_notify_missed_call) - .setColor(mContext.getResources().getColor(R.color.dialer_theme_color)) - // Show "Phone" for notification title. - .setContentTitle(mContext.getText(R.string.userCallActivityLabel)) - // Notification details shows that there are missed call(s), but does not reveal - // the missed caller information. - .setContentText(mContext.getText(titleResId)) + Notification.Builder publicSummaryBuilder = createNotificationBuilder(); + publicSummaryBuilder + .setContentTitle(context.getText(titleResId)) .setContentIntent(createCallLogPendingIntent()) - .setAutoCancel(true) - .setWhen(timeMs) - .setShowWhen(true) - .setDeleteIntent(createClearMissedCallsPendingIntent()); + .setDeleteIntent(createClearMissedCallsPendingIntent(null)); + // Create the notification summary suitable for display when sensitive information is showing. + groupSummary + .setContentTitle(context.getText(titleResId)) + .setContentText(expandedText) + .setContentIntent(createCallLogPendingIntent()) + .setDeleteIntent(createClearMissedCallsPendingIntent(null)) + .setGroupSummary(useCallList) + .setOnlyAlertOnce(useCallList) + .setPublicVersion(publicSummaryBuilder.build()); + + NotificationChannelManager.applyChannel( + groupSummary, + context, + Channel.MISSED_CALL, + PhoneAccountHandles.getAccount(context, useCallList ? newCalls.get(0) : null)); + + Notification notification = groupSummary.build(); + configureLedOnNotification(notification); + + LogUtil.i("MissedCallNotifier.updateMissedCallNotification", "adding missed call notification"); + getNotificationMgr().notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification); + + if (useCallList) { + // Do not repost active notifications to prevent erasing post call notes. + NotificationManager manager = getNotificationMgr(); + Set<String> activeTags = new HashSet<>(); + for (StatusBarNotification activeNotification : manager.getActiveNotifications()) { + activeTags.add(activeNotification.getTag()); + } + + for (NewCall call : newCalls) { + String callTag = call.callsUri.toString(); + if (!activeTags.contains(callTag)) { + manager.notify(callTag, NOTIFICATION_ID, getNotificationForCall(call, null)); + } + } + } + } + + public void insertPostCallNotification(@NonNull String number, @NonNull String note) { + List<NewCall> newCalls = callLogNotificationsQueryHelper.getNewMissedCalls(); + if (newCalls != null && !newCalls.isEmpty()) { + for (NewCall call : newCalls) { + if (call.number.equals(number.replace("tel:", ""))) { + // Update the first notification that matches our post call note sender. + getNotificationMgr() + .notify( + call.callsUri.toString(), NOTIFICATION_ID, getNotificationForCall(call, note)); + break; + } + } + } + } + + private Notification getNotificationForCall( + @NonNull NewCall call, @Nullable String postCallMessage) { + ContactInfo contactInfo = + callLogNotificationsQueryHelper.getContactInfo( + call.number, call.numberPresentation, call.countryIso); + + // Create a public viewable version of the notification, suitable for display when sensitive + // notification content is hidden. + int titleResId = + contactInfo.userType == ContactsUtils.USER_TYPE_WORK + ? R.string.notification_missedWorkCallTitle + : R.string.notification_missedCallTitle; + Notification.Builder publicBuilder = + createNotificationBuilder(call).setContentTitle(context.getText(titleResId)); + + Notification.Builder builder = createNotificationBuilder(call); + CharSequence expandedText; + if (TextUtils.equals(contactInfo.name, contactInfo.formattedNumber) + || TextUtils.equals(contactInfo.name, contactInfo.number)) { + expandedText = + PhoneNumberUtilsCompat.createTtsSpannable( + BidiFormatter.getInstance() + .unicodeWrap(contactInfo.name, TextDirectionHeuristics.LTR)); + } else { + expandedText = contactInfo.name; + } + + if (postCallMessage != null) { + expandedText = + context.getString(R.string.post_call_notification_message, expandedText, postCallMessage); + } + + ContactPhotoLoader loader = new ContactPhotoLoader(context, contactInfo); + Bitmap photoIcon = loader.loadPhotoIcon(); + if (photoIcon != null) { + builder.setLargeIcon(photoIcon); + } // Create the notification suitable for display when sensitive information is showing. builder - .setSmallIcon(android.R.drawable.stat_notify_missed_call) - .setColor(mContext.getResources().getColor(R.color.dialer_theme_color)) - .setContentTitle(mContext.getText(titleResId)) + .setContentTitle(context.getText(titleResId)) .setContentText(expandedText) - .setContentIntent(createCallLogPendingIntent()) - .setAutoCancel(true) - .setWhen(timeMs) - .setShowWhen(true) - .setDefaults(Notification.DEFAULT_VIBRATE) - .setDeleteIntent(createClearMissedCallsPendingIntent()) // Include a public version of the notification to be shown when the missed call // notification is shown on the user's lock screen and they have chosen to hide // sensitive notification information. .setPublicVersion(publicBuilder.build()); - // Add additional actions when there is only 1 missed call and the user isn't locked - if (UserManagerCompat.isUserUnlocked(mContext) && count == 1) { - if (!TextUtils.isEmpty(missedNumber) - && !TextUtils.equals(missedNumber, mContext.getString(R.string.handle_restricted))) { + // Add additional actions when the user isn't locked + if (UserManagerCompat.isUserUnlocked(context)) { + if (!TextUtils.isEmpty(call.number) + && !TextUtils.equals(call.number, context.getString(R.string.handle_restricted))) { builder.addAction( - R.drawable.ic_phone_24dp, - mContext.getString(R.string.notification_missedCall_call_back), - createCallBackPendingIntent(missedNumber)); + new Notification.Action.Builder( + Icon.createWithResource(context, R.drawable.ic_phone_24dp), + context.getString(R.string.notification_missedCall_call_back), + createCallBackPendingIntent(call.number, call.callsUri)) + .build()); - if (!PhoneNumberHelper.isUriNumber(missedNumber)) { + if (!PhoneNumberHelper.isUriNumber(call.number)) { builder.addAction( - R.drawable.ic_message_24dp, - mContext.getString(R.string.notification_missedCall_message), - createSendSmsFromNotificationPendingIntent(missedNumber)); + new Notification.Action.Builder( + Icon.createWithResource(context, R.drawable.ic_message_24dp), + context.getString(R.string.notification_missedCall_message), + createSendSmsFromNotificationPendingIntent(call.number, call.callsUri)) + .build()); } } } Notification notification = builder.build(); configureLedOnNotification(notification); + return notification; + } - LogUtil.i("MissedCallNotifier.updateMissedCallNotification", "adding missed call notification"); - getNotificationMgr().notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification); + private Notification.Builder createNotificationBuilder() { + return new Notification.Builder(context) + .setGroup(NOTIFICATION_TAG) + .setSmallIcon(android.R.drawable.stat_notify_missed_call) + .setColor(context.getResources().getColor(R.color.dialer_theme_color, null)) + .setAutoCancel(true) + .setOnlyAlertOnce(true) + .setShowWhen(true) + .setDefaults(Notification.DEFAULT_VIBRATE); } - private void clearMissedCalls() { - AsyncTask.execute( - new Runnable() { - @Override - public void run() { - // Call log is only accessible when unlocked. If that's the case, clear the list of - // new missed calls from the call log. - if (UserManagerCompat.isUserUnlocked(mContext)) { - ContentValues values = new ContentValues(); - values.put(Calls.NEW, 0); - values.put(Calls.IS_READ, 1); - StringBuilder where = new StringBuilder(); - where.append(Calls.NEW); - where.append(" = 1 AND "); - where.append(Calls.TYPE); - where.append(" = ?"); - try { - mContext - .getContentResolver() - .update( - Calls.CONTENT_URI, - values, - where.toString(), - new String[] {Integer.toString(Calls.MISSED_TYPE)}); - } catch (IllegalArgumentException e) { - LogUtil.e( - "MissedCallNotifier.clearMissedCalls", - "contacts provider update command failed", - e); - } - } - getNotificationMgr().cancel(NOTIFICATION_TAG, NOTIFICATION_ID); - } - }); + private Notification.Builder createNotificationBuilder(@NonNull NewCall call) { + Builder builder = + createNotificationBuilder() + .setWhen(call.dateMs) + .setDeleteIntent(createClearMissedCallsPendingIntent(call.callsUri)) + .setContentIntent(createCallLogPendingIntent(call.callsUri)); + + NotificationChannelManager.applyChannel( + builder, context, Channel.MISSED_CALL, PhoneAccountHandles.getAccount(context, call)); + return builder; } /** Trigger an intent to make a call from a missed call number. */ - public void callBackFromMissedCall(String number) { - closeSystemDialogs(mContext); - CallLogNotificationsHelper.removeMissedCallNotifications(mContext); + @WorkerThread + public void callBackFromMissedCall(String number, Uri callUri) { + closeSystemDialogs(context); + CallLogNotificationsQueryHelper.removeMissedCallNotifications(context, callUri); + TelecomUtil.cancelMissedCallsNotification(context); DialerUtils.startActivityWithErrorToast( - mContext, + context, new CallIntentBuilder(number, CallInitiationType.Type.MISSED_CALL_NOTIFICATION) .build() .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } /** Trigger an intent to send an sms from a missed call number. */ - public void sendSmsFromMissedCall(String number) { - closeSystemDialogs(mContext); - CallLogNotificationsHelper.removeMissedCallNotifications(mContext); + @WorkerThread + public void sendSmsFromMissedCall(String number, Uri callUri) { + closeSystemDialogs(context); + CallLogNotificationsQueryHelper.removeMissedCallNotifications(context, callUri); + TelecomUtil.cancelMissedCallsNotification(context); DialerUtils.startActivityWithErrorToast( - mContext, IntentUtil.getSendSmsIntent(number).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + context, IntentUtil.getSendSmsIntent(number).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } /** @@ -283,34 +356,50 @@ public class MissedCallNotifier { * @return The pending intent. */ private PendingIntent createCallLogPendingIntent() { + return createCallLogPendingIntent(null); + } + + /** + * Creates a new pending intent that sends the user to the call log. + * + * @return The pending intent. + * @param callUri Uri of the call to jump to. May be null + */ + private PendingIntent createCallLogPendingIntent(@Nullable Uri callUri) { Intent contentIntent = - DialtactsActivity.getShowTabIntent(mContext, ListsFragment.TAB_INDEX_HISTORY); - return PendingIntent.getActivity(mContext, 0, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT); + DialtactsActivity.getShowTabIntent(context, ListsFragment.TAB_INDEX_HISTORY); + // TODO (b/35486204): scroll to call + contentIntent.setData(callUri); + return PendingIntent.getActivity(context, 0, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT); } /** Creates a pending intent that marks all new missed calls as old. */ - private PendingIntent createClearMissedCallsPendingIntent() { - Intent intent = new Intent(mContext, CallLogNotificationsService.class); + private PendingIntent createClearMissedCallsPendingIntent(@Nullable Uri callUri) { + Intent intent = new Intent(context, CallLogNotificationsService.class); intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_MISSED_CALLS_AS_OLD); - return PendingIntent.getService(mContext, 0, intent, 0); + intent.setData(callUri); + return PendingIntent.getService(context, 0, intent, 0); } - private PendingIntent createCallBackPendingIntent(String number) { - Intent intent = new Intent(mContext, CallLogNotificationsService.class); + private PendingIntent createCallBackPendingIntent(String number, @NonNull Uri callUri) { + Intent intent = new Intent(context, CallLogNotificationsService.class); intent.setAction(CallLogNotificationsService.ACTION_CALL_BACK_FROM_MISSED_CALL_NOTIFICATION); intent.putExtra(CallLogNotificationsService.EXTRA_MISSED_CALL_NUMBER, number); + intent.setData(callUri); // Use FLAG_UPDATE_CURRENT to make sure any previous pending intent is updated with the new // extra. - return PendingIntent.getService(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); } - private PendingIntent createSendSmsFromNotificationPendingIntent(String number) { - Intent intent = new Intent(mContext, CallLogNotificationsService.class); + private PendingIntent createSendSmsFromNotificationPendingIntent( + String number, @NonNull Uri callUri) { + Intent intent = new Intent(context, CallLogNotificationsService.class); intent.setAction(CallLogNotificationsService.ACTION_SEND_SMS_FROM_MISSED_CALL_NOTIFICATION); intent.putExtra(CallLogNotificationsService.EXTRA_MISSED_CALL_NUMBER, number); + intent.setData(callUri); // Use FLAG_UPDATE_CURRENT to make sure any previous pending intent is updated with the new // extra. - return PendingIntent.getService(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); } /** Configures a notification to emit the blinky notification light. */ @@ -325,6 +414,6 @@ public class MissedCallNotifier { } private NotificationManager getNotificationMgr() { - return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); } } diff --git a/java/com/android/dialer/app/calllog/PhoneAccountHandles.java b/java/com/android/dialer/app/calllog/PhoneAccountHandles.java new file mode 100644 index 000000000..6d51b853c --- /dev/null +++ b/java/com/android/dialer/app/calllog/PhoneAccountHandles.java @@ -0,0 +1,41 @@ +package com.android.dialer.app.calllog; + +import android.content.ComponentName; +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.telecom.PhoneAccount; +import android.telecom.PhoneAccountHandle; +import com.android.dialer.app.calllog.CallLogNotificationsQueryHelper.NewCall; +import com.android.dialer.common.LogUtil; +import com.android.dialer.telecom.TelecomUtil; + +/** Methods to help extract {@link PhoneAccount} information from database and Telecomm sources. */ +class PhoneAccountHandles { + + @Nullable + public static PhoneAccountHandle getAccount(@NonNull Context context, @Nullable NewCall call) { + PhoneAccountHandle handle; + if (call == null || call.accountComponentName == null || call.accountId == null) { + LogUtil.v( + "PhoneAccountUtils.getAccount", + "accountComponentName == null || callToNotify.accountId == null"); + handle = TelecomUtil.getDefaultOutgoingPhoneAccount(context, PhoneAccount.SCHEME_TEL); + if (handle == null) { + return null; + } + } else { + handle = + new PhoneAccountHandle( + ComponentName.unflattenFromString(call.accountComponentName), call.accountId); + } + if (handle.getComponentName() != null) { + LogUtil.v( + "PhoneAccountUtils.getAccount", + "PhoneAccountHandle.ComponentInfo:" + handle.getComponentName()); + } else { + LogUtil.i("PhoneAccountUtils.getAccount", "PhoneAccountHandle.ComponentInfo: null"); + } + return handle; + } +} diff --git a/java/com/android/dialer/app/calllog/PhoneAccountUtils.java b/java/com/android/dialer/app/calllog/PhoneAccountUtils.java deleted file mode 100644 index c6d94d341..000000000 --- a/java/com/android/dialer/app/calllog/PhoneAccountUtils.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2013 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.app.calllog; - -import android.content.ComponentName; -import android.content.Context; -import android.support.annotation.Nullable; -import android.telecom.PhoneAccount; -import android.telecom.PhoneAccountHandle; -import android.text.TextUtils; -import com.android.dialer.telecom.TelecomUtil; -import java.util.ArrayList; -import java.util.List; - -/** Methods to help extract {@code PhoneAccount} information from database and Telecomm sources. */ -public class PhoneAccountUtils { - - /** Return a list of phone accounts that are subscription/SIM accounts. */ - public static List<PhoneAccountHandle> getSubscriptionPhoneAccounts(Context context) { - List<PhoneAccountHandle> subscriptionAccountHandles = new ArrayList<PhoneAccountHandle>(); - final List<PhoneAccountHandle> accountHandles = - TelecomUtil.getCallCapablePhoneAccounts(context); - for (PhoneAccountHandle accountHandle : accountHandles) { - PhoneAccount account = TelecomUtil.getPhoneAccount(context, accountHandle); - if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { - subscriptionAccountHandles.add(accountHandle); - } - } - return subscriptionAccountHandles; - } - - /** Compose PhoneAccount object from component name and account id. */ - @Nullable - public static PhoneAccountHandle getAccount( - @Nullable String componentString, @Nullable String accountId) { - if (TextUtils.isEmpty(componentString) || TextUtils.isEmpty(accountId)) { - return null; - } - final ComponentName componentName = ComponentName.unflattenFromString(componentString); - if (componentName == null) { - return null; - } - return new PhoneAccountHandle(componentName, accountId); - } - - /** Extract account label from PhoneAccount object. */ - @Nullable - public static String getAccountLabel( - Context context, @Nullable PhoneAccountHandle accountHandle) { - PhoneAccount account = getAccountOrNull(context, accountHandle); - if (account != null && account.getLabel() != null) { - return account.getLabel().toString(); - } - return null; - } - - /** Extract account color from PhoneAccount object. */ - public static int getAccountColor(Context context, @Nullable PhoneAccountHandle accountHandle) { - final PhoneAccount account = TelecomUtil.getPhoneAccount(context, accountHandle); - - // For single-sim devices the PhoneAccount will be NO_HIGHLIGHT_COLOR by default, so it is - // safe to always use the account highlight color. - return account == null ? PhoneAccount.NO_HIGHLIGHT_COLOR : account.getHighlightColor(); - } - - /** - * Determine whether a phone account supports call subjects. - * - * @return {@code true} if call subjects are supported, {@code false} otherwise. - */ - public static boolean getAccountSupportsCallSubject( - Context context, @Nullable PhoneAccountHandle accountHandle) { - final PhoneAccount account = TelecomUtil.getPhoneAccount(context, accountHandle); - - return account != null && account.hasCapabilities(PhoneAccount.CAPABILITY_CALL_SUBJECT); - } - - /** - * Retrieve the account metadata, but if the account does not exist or the device has only a - * single registered and enabled account, return null. - */ - @Nullable - private static PhoneAccount getAccountOrNull( - Context context, @Nullable PhoneAccountHandle accountHandle) { - if (TelecomUtil.getCallCapablePhoneAccounts(context).size() <= 1) { - return null; - } - return TelecomUtil.getPhoneAccount(context, accountHandle); - } -} diff --git a/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java b/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java index b18270bb3..acbccb39f 100644 --- a/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java +++ b/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java @@ -27,9 +27,10 @@ import android.text.TextUtils; import android.text.format.DateUtils; import android.view.View; import android.widget.TextView; -import com.android.dialer.app.PhoneCallDetails; import com.android.dialer.app.R; import com.android.dialer.app.calllog.calllogcache.CallLogCache; +import com.android.dialer.calllogutils.PhoneCallDetails; +import com.android.dialer.oem.MotorolaUtils; import com.android.dialer.phonenumberutil.PhoneNumberHelper; import com.android.dialer.util.DialerUtils; import java.util.ArrayList; @@ -84,6 +85,8 @@ public class PhoneCallDetailsHelper { // Show the video icon if the call had video enabled. views.callTypeIcons.setShowVideo( (details.features & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO); + views.callTypeIcons.setShowHd( + MotorolaUtils.shouldShowHdIconInCallLog(mContext, details.features)); views.callTypeIcons.requestLayout(); views.callTypeIcons.setVisibility(View.VISIBLE); diff --git a/java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java b/java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java index 476996826..e2e27a179 100644 --- a/java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java +++ b/java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java @@ -20,6 +20,7 @@ import android.content.Context; import android.view.View; import android.widget.TextView; import com.android.dialer.app.R; +import com.android.dialer.calllogutils.CallTypeIconsView; /** Encapsulates the views that are used to display the details of a phone call in the call log. */ public final class PhoneCallDetailsViews { diff --git a/java/com/android/dialer/app/calllog/PhoneNumberDisplayUtil.java b/java/com/android/dialer/app/calllog/PhoneNumberDisplayUtil.java deleted file mode 100644 index 410d4cc37..000000000 --- a/java/com/android/dialer/app/calllog/PhoneNumberDisplayUtil.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2011 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.app.calllog; - -import android.content.Context; -import android.provider.CallLog.Calls; -import android.text.BidiFormatter; -import android.text.TextDirectionHeuristics; -import android.text.TextUtils; -import com.android.contacts.common.compat.PhoneNumberUtilsCompat; -import com.android.dialer.app.R; -import com.android.dialer.phonenumberutil.PhoneNumberHelper; - -/** Helper for formatting and managing the display of phone numbers. */ -public class PhoneNumberDisplayUtil { - - /** Returns the string to display for the given phone number if there is no matching contact. */ - /* package */ - static CharSequence getDisplayName( - Context context, CharSequence number, int presentation, boolean isVoicemail) { - if (presentation == Calls.PRESENTATION_UNKNOWN) { - return context.getResources().getString(R.string.unknown); - } - if (presentation == Calls.PRESENTATION_RESTRICTED) { - return PhoneNumberHelper.getDisplayNameForRestrictedNumber(context); - } - if (presentation == Calls.PRESENTATION_PAYPHONE) { - return context.getResources().getString(R.string.payphone); - } - if (isVoicemail) { - return context.getResources().getString(R.string.voicemail); - } - if (PhoneNumberHelper.isLegacyUnknownNumbers(number)) { - return context.getResources().getString(R.string.unknown); - } - return ""; - } - - /** - * Returns the string to display for the given phone number. - * - * @param number the number to display - * @param formattedNumber the formatted number if available, may be null - */ - public static CharSequence getDisplayNumber( - Context context, - CharSequence number, - int presentation, - CharSequence formattedNumber, - CharSequence postDialDigits, - boolean isVoicemail) { - final CharSequence displayName = getDisplayName(context, number, presentation, isVoicemail); - if (!TextUtils.isEmpty(displayName)) { - return getTtsSpannableLtrNumber(displayName); - } - - if (!TextUtils.isEmpty(formattedNumber)) { - return getTtsSpannableLtrNumber(formattedNumber); - } else if (!TextUtils.isEmpty(number)) { - return getTtsSpannableLtrNumber(number.toString() + postDialDigits); - } else { - return context.getResources().getString(R.string.unknown); - } - } - - /** Returns number annotated as phone number in LTR direction. */ - public static CharSequence getTtsSpannableLtrNumber(CharSequence number) { - return PhoneNumberUtilsCompat.createTtsSpannable( - BidiFormatter.getInstance().unicodeWrap(number.toString(), TextDirectionHeuristics.LTR)); - } -} diff --git a/java/com/android/dialer/app/calllog/VisualVoicemailCallLogFragment.java b/java/com/android/dialer/app/calllog/VisualVoicemailCallLogFragment.java index e539ceef6..6f101f580 100644 --- a/java/com/android/dialer/app/calllog/VisualVoicemailCallLogFragment.java +++ b/java/com/android/dialer/app/calllog/VisualVoicemailCallLogFragment.java @@ -40,10 +40,13 @@ public class VisualVoicemailCallLogFragment extends CallLogFragment { private VoicemailErrorManager mVoicemailAlertManager; + public VisualVoicemailCallLogFragment() { + super(CallLog.Calls.VOICEMAIL_TYPE); + } + @Override public void onCreate(Bundle state) { super.onCreate(state); - mCallTypeFilter = CallLog.Calls.VOICEMAIL_TYPE; mVoicemailPlaybackPresenter = VoicemailPlaybackPresenter.getInstance(getActivity(), state); getActivity() .getContentResolver() diff --git a/java/com/android/dialer/app/calllog/VoicemailQueryHandler.java b/java/com/android/dialer/app/calllog/VoicemailQueryHandler.java index d6d8354ec..e73684e70 100644 --- a/java/com/android/dialer/app/calllog/VoicemailQueryHandler.java +++ b/java/com/android/dialer/app/calllog/VoicemailQueryHandler.java @@ -15,13 +15,18 @@ */ package com.android.dialer.app.calllog; +import android.app.NotificationManager; import android.content.AsyncQueryHandler; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; +import android.net.Uri; import android.provider.CallLog.Calls; -import android.util.Log; +import android.support.annotation.Nullable; +import com.android.dialer.app.R; +import com.android.dialer.common.LogUtil; +import com.android.dialer.notification.GroupedNotificationUtil; /** Handles asynchronous queries to the call log for voicemail. */ public class VoicemailQueryHandler extends AsyncQueryHandler { @@ -39,7 +44,7 @@ public class VoicemailQueryHandler extends AsyncQueryHandler { } /** Updates all new voicemails to mark them as old. */ - public void markNewVoicemailsAsOld() { + public void markNewVoicemailsAsOld(@Nullable Uri voicemailUri) { // Mark all "new" voicemails as not new anymore. StringBuilder where = new StringBuilder(); where.append(Calls.NEW); @@ -47,6 +52,10 @@ public class VoicemailQueryHandler extends AsyncQueryHandler { where.append(Calls.TYPE); where.append(" = ?"); + if (voicemailUri != null) { + where.append(" AND ").append(Calls.VOICEMAIL_URI).append(" = ?"); + } + ContentValues values = new ContentValues(1); values.put(Calls.NEW, "0"); @@ -56,7 +65,15 @@ public class VoicemailQueryHandler extends AsyncQueryHandler { Calls.CONTENT_URI_WITH_VOICEMAIL, values, where.toString(), - new String[] {Integer.toString(Calls.VOICEMAIL_TYPE)}); + voicemailUri == null + ? new String[] {Integer.toString(Calls.VOICEMAIL_TYPE)} + : new String[] {Integer.toString(Calls.VOICEMAIL_TYPE), voicemailUri.toString()}); + + GroupedNotificationUtil.removeNotification( + mContext.getSystemService(NotificationManager.class), + voicemailUri != null ? voicemailUri.toString() : null, + R.id.notification_voicemail, + DefaultVoicemailNotifier.NOTIFICATION_TAG); } @Override @@ -67,7 +84,7 @@ public class VoicemailQueryHandler extends AsyncQueryHandler { serviceIntent.setAction(CallLogNotificationsService.ACTION_UPDATE_VOICEMAIL_NOTIFICATIONS); mContext.startService(serviceIntent); } else { - Log.w(TAG, "Unknown update completed: ignoring: " + token); + LogUtil.w(TAG, "Unknown update completed: ignoring: " + token); } } } diff --git a/java/com/android/dialer/app/calllog/calllogcache/CallLogCacheLollipopMr1.java b/java/com/android/dialer/app/calllog/calllogcache/CallLogCacheLollipopMr1.java index c342b7e3b..039998780 100644 --- a/java/com/android/dialer/app/calllog/calllogcache/CallLogCacheLollipopMr1.java +++ b/java/com/android/dialer/app/calllog/calllogcache/CallLogCacheLollipopMr1.java @@ -20,10 +20,10 @@ import android.content.Context; import android.support.annotation.VisibleForTesting; import android.telecom.PhoneAccountHandle; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.Pair; -import com.android.dialer.app.calllog.PhoneAccountUtils; +import com.android.dialer.calllogutils.PhoneAccountUtils; import com.android.dialer.phonenumberutil.PhoneNumberHelper; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -45,9 +45,9 @@ class CallLogCacheLollipopMr1 extends CallLogCache { final Map<Pair<PhoneAccountHandle, CharSequence>, Boolean> mVoicemailQueryCache = new ConcurrentHashMap<>(); - private final Map<PhoneAccountHandle, String> mPhoneAccountLabelCache = new HashMap<>(); - private final Map<PhoneAccountHandle, Integer> mPhoneAccountColorCache = new HashMap<>(); - private final Map<PhoneAccountHandle, Boolean> mPhoneAccountCallWithNoteCache = new HashMap<>(); + private final Map<PhoneAccountHandle, String> mPhoneAccountLabelCache = new ArrayMap<>(); + private final Map<PhoneAccountHandle, Integer> mPhoneAccountColorCache = new ArrayMap<>(); + private final Map<PhoneAccountHandle, Boolean> mPhoneAccountCallWithNoteCache = new ArrayMap<>(); /* package */ CallLogCacheLollipopMr1(Context context) { super(context); diff --git a/java/com/android/dialer/app/contactinfo/ContactInfoCache.java b/java/com/android/dialer/app/contactinfo/ContactInfoCache.java index 4135cb7b8..6c35711a8 100644 --- a/java/com/android/dialer/app/contactinfo/ContactInfoCache.java +++ b/java/com/android/dialer/app/contactinfo/ContactInfoCache.java @@ -29,8 +29,8 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.PriorityBlockingQueue; /** - * This is a cache of contact details for the phone numbers in the c all log. The key is the phone - * number with the country in which teh call was placed or received. The content of the cache is + * This is a cache of contact details for the phone numbers in the call log. The key is the phone + * number with the country in which the call was placed or received. The content of the cache is * expired (but not purged) whenever the application comes to the foreground. * * <p>This cache queues request for information and queries for information on a background thread, diff --git a/java/com/android/dialer/app/contactinfo/ContactPhotoLoader.java b/java/com/android/dialer/app/contactinfo/ContactPhotoLoader.java index a8c718502..71e4a16ad 100644 --- a/java/com/android/dialer/app/contactinfo/ContactPhotoLoader.java +++ b/java/com/android/dialer/app/contactinfo/ContactPhotoLoader.java @@ -104,7 +104,7 @@ public class ContactPhotoLoader { final RoundedBitmapDrawable drawable = RoundedBitmapDrawableFactory.create(mContext.getResources(), bitmap); drawable.setAntiAlias(true); - drawable.setCornerRadius(bitmap.getHeight() / 2); + drawable.setCircular(true); return drawable; } catch (IOException e) { LogUtil.e("ContactPhotoLoader.createPhotoIconDrawable", e.toString()); diff --git a/java/com/android/dialer/app/dialpad/DialpadFragment.java b/java/com/android/dialer/app/dialpad/DialpadFragment.java index 18bb250ce..4785ab16f 100644 --- a/java/com/android/dialer/app/dialpad/DialpadFragment.java +++ b/java/com/android/dialer/app/dialpad/DialpadFragment.java @@ -78,9 +78,9 @@ import com.android.dialer.app.DialtactsActivity; import com.android.dialer.app.R; import com.android.dialer.app.SpecialCharSequenceMgr; import com.android.dialer.app.calllog.CallLogAsync; -import com.android.dialer.app.calllog.PhoneAccountUtils; import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.callintent.nano.CallInitiationType; +import com.android.dialer.calllogutils.PhoneAccountUtils; import com.android.dialer.common.LogUtil; import com.android.dialer.dialpadview.DialpadKeyButton; import com.android.dialer.dialpadview.DialpadView; @@ -598,6 +598,7 @@ public class DialpadFragment extends Fragment @Override public void onStart() { + LogUtil.d("DialpadFragment.onStart", "first launch: %b", mFirstLaunch); Trace.beginSection(TAG + " onStart"); super.onStart(); // if the mToneGenerator creation fails, just continue without it. It is @@ -624,6 +625,7 @@ public class DialpadFragment extends Fragment @Override public void onResume() { + LogUtil.d("DialpadFragment.onResume", ""); Trace.beginSection(TAG + " onResume"); super.onResume(); diff --git a/java/com/android/dialer/app/filterednumber/BlockedNumbersSettingsActivity.java b/java/com/android/dialer/app/filterednumber/BlockedNumbersSettingsActivity.java index eef920710..9ec6042c0 100644 --- a/java/com/android/dialer/app/filterednumber/BlockedNumbersSettingsActivity.java +++ b/java/com/android/dialer/app/filterednumber/BlockedNumbersSettingsActivity.java @@ -135,11 +135,6 @@ public class BlockedNumbersSettingsActivity extends AppCompatActivity } @Override - public int getActionBarHideOffset() { - return 0; - } - - @Override public int getActionBarHeight() { return 0; } diff --git a/java/com/android/dialer/app/legacybindings/DialerLegacyBindings.java b/java/com/android/dialer/app/legacybindings/DialerLegacyBindings.java index 2125a1524..1cdeb2175 100644 --- a/java/com/android/dialer/app/legacybindings/DialerLegacyBindings.java +++ b/java/com/android/dialer/app/legacybindings/DialerLegacyBindings.java @@ -17,12 +17,14 @@ package com.android.dialer.app.legacybindings; import android.app.Activity; +import android.support.annotation.NonNull; import android.view.ViewGroup; import com.android.dialer.app.calllog.CallLogAdapter; import com.android.dialer.app.calllog.calllogcache.CallLogCache; import com.android.dialer.app.contactinfo.ContactInfoCache; import com.android.dialer.app.list.RegularSearchFragment; import com.android.dialer.app.voicemail.VoicemailPlaybackPresenter; +import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler; /** * These are old bindings between Dialer and the container application. All new bindings should be @@ -41,6 +43,7 @@ public interface DialerLegacyBindings { CallLogCache callLogCache, ContactInfoCache contactInfoCache, VoicemailPlaybackPresenter voicemailPlaybackPresenter, + @NonNull FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler, int activityType); RegularSearchFragment newRegularSearchFragment(); diff --git a/java/com/android/dialer/app/legacybindings/DialerLegacyBindingsStub.java b/java/com/android/dialer/app/legacybindings/DialerLegacyBindingsStub.java index f01df78f8..6e32843ba 100644 --- a/java/com/android/dialer/app/legacybindings/DialerLegacyBindingsStub.java +++ b/java/com/android/dialer/app/legacybindings/DialerLegacyBindingsStub.java @@ -17,12 +17,14 @@ package com.android.dialer.app.legacybindings; import android.app.Activity; +import android.support.annotation.NonNull; import android.view.ViewGroup; import com.android.dialer.app.calllog.CallLogAdapter; import com.android.dialer.app.calllog.calllogcache.CallLogCache; import com.android.dialer.app.contactinfo.ContactInfoCache; import com.android.dialer.app.list.RegularSearchFragment; import com.android.dialer.app.voicemail.VoicemailPlaybackPresenter; +import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler; /** Default implementation for dialer legacy bindings. */ public class DialerLegacyBindingsStub implements DialerLegacyBindings { @@ -35,6 +37,7 @@ public class DialerLegacyBindingsStub implements DialerLegacyBindings { CallLogCache callLogCache, ContactInfoCache contactInfoCache, VoicemailPlaybackPresenter voicemailPlaybackPresenter, + @NonNull FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler, int activityType) { return new CallLogAdapter( activity, @@ -43,6 +46,7 @@ public class DialerLegacyBindingsStub implements DialerLegacyBindings { callLogCache, contactInfoCache, voicemailPlaybackPresenter, + filteredNumberAsyncQueryHandler, activityType); } diff --git a/java/com/android/dialer/app/list/ListsFragment.java b/java/com/android/dialer/app/list/ListsFragment.java index 725ad3001..13938f29a 100644 --- a/java/com/android/dialer/app/list/ListsFragment.java +++ b/java/com/android/dialer/app/list/ListsFragment.java @@ -30,19 +30,16 @@ import android.support.annotation.Nullable; import android.support.v13.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager.OnPageChangeListener; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.android.contacts.common.list.ViewPagerTabs; import com.android.dialer.app.R; import com.android.dialer.app.calllog.CallLogFragment; -import com.android.dialer.app.calllog.CallLogNotificationsHelper; +import com.android.dialer.app.calllog.CallLogNotificationsService; import com.android.dialer.app.calllog.VisualVoicemailCallLogFragment; import com.android.dialer.app.voicemail.error.VoicemailStatusCorruptionHandler; import com.android.dialer.app.voicemail.error.VoicemailStatusCorruptionHandler.Source; -import com.android.dialer.app.widget.ActionBarController; import com.android.dialer.common.LogUtil; import com.android.dialer.database.CallLogQueryHandler; import com.android.dialer.logging.Logger; @@ -92,7 +89,6 @@ public class ListsFragment extends Fragment public static final int TAB_COUNT_DEFAULT = 3; public static final int TAB_COUNT_WITH_VOICEMAIL = 4; private static final String TAG = "ListsFragment"; - private ActionBar mActionBar; private ViewPager mViewPager; private ViewPagerTabs mViewPagerTabs; private ViewPagerAdapter mViewPagerAdapter; @@ -108,8 +104,7 @@ public class ListsFragment extends Fragment private boolean mHasFetchedVoicemailStatus; private boolean mShowVoicemailTabAfterVoicemailStatusIsFetched; private VoicemailStatusHelper mVoicemailStatusHelper; - private ArrayList<OnPageChangeListener> mOnPageChangeListeners = - new ArrayList<OnPageChangeListener>(); + private final ArrayList<OnPageChangeListener> mOnPageChangeListeners = new ArrayList<>(); private String[] mTabTitles; private int[] mTabIcons; /** The position of the currently selected tab. */ @@ -149,7 +144,6 @@ public class ListsFragment extends Fragment Trace.beginSection(TAG + " onResume"); super.onResume(); - mActionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); if (getUserVisibleHint()) { sendScreenViewForCurrentPosition(); } @@ -329,7 +323,7 @@ public class ListsFragment extends Fragment .putBoolean( VisualVoicemailEnabledChecker.PREF_KEY_HAS_ACTIVE_VOICEMAIL_PROVIDER, hasActiveVoicemailProvider) - .commit(); + .apply(); } if (hasActiveVoicemailProvider) { @@ -403,7 +397,7 @@ public class ListsFragment extends Fragment public void markMissedCallsAsReadAndRemoveNotifications() { if (mCallLogQueryHandler != null) { mCallLogQueryHandler.markMissedCallsAsRead(); - CallLogNotificationsHelper.removeMissedCallNotifications(getActivity()); + CallLogNotificationsService.markNewMissedCallsAsOld(getContext(), null); } } @@ -413,11 +407,6 @@ public class ListsFragment extends Fragment mRemoveView.animate().alpha(show ? 1 : 0).start(); } - public boolean shouldShowActionBar() { - // TODO: Update this based on scroll state. - return mActionBar != null; - } - public SpeedDialFragment getSpeedDialFragment() { return mSpeedDialFragment; } @@ -486,11 +475,6 @@ public class ListsFragment extends Fragment throw new IllegalStateException("No fragment at position " + position); } - public interface HostInterface { - - ActionBarController getActionBarController(); - } - public class ViewPagerAdapter extends FragmentPagerAdapter { private final List<Fragment> mFragments = new ArrayList<>(); @@ -518,7 +502,7 @@ public class ListsFragment extends Fragment return mSpeedDialFragment; case TAB_INDEX_HISTORY: if (mHistoryFragment == null) { - mHistoryFragment = new CallLogFragment(); + mHistoryFragment = new CallLogFragment(CallLogQueryHandler.CALL_TYPE_ALL); } return mHistoryFragment; case TAB_INDEX_ALL_CONTACTS: diff --git a/java/com/android/dialer/app/list/SearchFragment.java b/java/com/android/dialer/app/list/SearchFragment.java index 4a7d48ae4..e6615aa8d 100644 --- a/java/com/android/dialer/app/list/SearchFragment.java +++ b/java/com/android/dialer/app/list/SearchFragment.java @@ -98,6 +98,7 @@ public class SearchFragment extends PhoneNumberPickerFragment { @Override public void onStart() { + LogUtil.d("SearchFragment.onStart", ""); super.onStart(); if (isSearchMode()) { getAdapter().setHasHeader(0, false); @@ -301,6 +302,7 @@ public class SearchFragment extends PhoneNumberPickerFragment { * shown. This can be optionally animated. */ public void updatePosition(boolean animate) { + LogUtil.d("SearchFragment.updatePosition", "animate: %b", animate); if (mActivity == null) { // Activity will be set in onStart, and this method will be called again return; @@ -363,6 +365,13 @@ public class SearchFragment extends PhoneNumberPickerFragment { return; } int spacerHeight = mActivity.isDialpadShown() ? mActivity.getDialpadHeight() : 0; + LogUtil.d( + "SearchFragment.resizeListView", + "spacerHeight: %d -> %d, isDialpadShown: %b, dialpad height: %d", + mSpacer.getHeight(), + spacerHeight, + mActivity.isDialpadShown(), + mActivity.getDialpadHeight()); if (spacerHeight != mSpacer.getHeight()) { final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mSpacer.getLayoutParams(); lp.height = spacerHeight; @@ -418,8 +427,6 @@ public class SearchFragment extends PhoneNumberPickerFragment { int getDialpadHeight(); - int getActionBarHideOffset(); - int getActionBarHeight(); } } diff --git a/java/com/android/dialer/app/manifests/activities/AndroidManifest.xml b/java/com/android/dialer/app/manifests/activities/AndroidManifest.xml index 247b34f4c..7e450c4cd 100644 --- a/java/com/android/dialer/app/manifests/activities/AndroidManifest.xml +++ b/java/com/android/dialer/app/manifests/activities/AndroidManifest.xml @@ -29,18 +29,6 @@ android:theme="@style/SettingsStyle"> </activity> - <activity - android:label="@string/callDetailTitle" - android:name="com.android.dialer.app.CallDetailActivity" - android:parentActivityName="com.android.dialer.calllog.CallLogActivity" - android:theme="@style/CallDetailActivityTheme"> - <intent-filter> - <action android:name="android.intent.action.VIEW"/> - <category android:name="android.intent.category.DEFAULT"/> - <data android:mimeType="vnd.android.cursor.item/calls"/> - </intent-filter> - </activity> - <!-- The entrance point for Phone UI. stateAlwaysHidden is set to suppress keyboard show up on dialpad screen. --> diff --git a/java/com/android/dialer/app/res/drawable-hdpi/ic_call_arrow.png b/java/com/android/dialer/app/res/drawable-hdpi/ic_call_arrow.png Binary files differdeleted file mode 100644 index 14a33e39f..000000000 --- a/java/com/android/dialer/app/res/drawable-hdpi/ic_call_arrow.png +++ /dev/null diff --git a/java/com/android/dialer/app/res/drawable-mdpi/ic_call_arrow.png b/java/com/android/dialer/app/res/drawable-mdpi/ic_call_arrow.png Binary files differdeleted file mode 100644 index 169cf2934..000000000 --- a/java/com/android/dialer/app/res/drawable-mdpi/ic_call_arrow.png +++ /dev/null diff --git a/java/com/android/dialer/app/res/drawable-xhdpi/ic_call_arrow.png b/java/com/android/dialer/app/res/drawable-xhdpi/ic_call_arrow.png Binary files differdeleted file mode 100644 index 6f1366018..000000000 --- a/java/com/android/dialer/app/res/drawable-xhdpi/ic_call_arrow.png +++ /dev/null diff --git a/java/com/android/dialer/app/res/drawable-xxhdpi/ic_call_arrow.png b/java/com/android/dialer/app/res/drawable-xxhdpi/ic_call_arrow.png Binary files differdeleted file mode 100644 index 0364ee015..000000000 --- a/java/com/android/dialer/app/res/drawable-xxhdpi/ic_call_arrow.png +++ /dev/null diff --git a/java/com/android/dialer/app/res/drawable-xxxhdpi/ic_call_arrow.png b/java/com/android/dialer/app/res/drawable-xxxhdpi/ic_call_arrow.png Binary files differdeleted file mode 100644 index 8243c2536..000000000 --- a/java/com/android/dialer/app/res/drawable-xxxhdpi/ic_call_arrow.png +++ /dev/null diff --git a/java/com/android/dialer/app/res/drawable-xxxhdpi/search_shadow.9.png b/java/com/android/dialer/app/res/drawable-xxxhdpi/search_shadow.9.png Binary files differnew file mode 100644 index 000000000..ff55620d0 --- /dev/null +++ b/java/com/android/dialer/app/res/drawable-xxxhdpi/search_shadow.9.png diff --git a/java/com/android/dialer/app/res/layout/call_detail.xml b/java/com/android/dialer/app/res/layout/call_detail.xml deleted file mode 100644 index 58a7bf0dc..000000000 --- a/java/com/android/dialer/app/res/layout/call_detail.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2009 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. ---> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/call_detail" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@color/background_dialer_call_log"> - - <!-- - The list view is under everything. - It contains a first header element which is hidden under the controls UI. - When scrolling, the controls move up until the name bar hits the top. - --> - <ListView - android:id="@+id/history" - android:layout_width="match_parent" - android:layout_height="fill_parent"/> - -</FrameLayout> diff --git a/java/com/android/dialer/app/res/layout/call_detail_footer.xml b/java/com/android/dialer/app/res/layout/call_detail_footer.xml deleted file mode 100644 index 57713448e..000000000 --- a/java/com/android/dialer/app/res/layout/call_detail_footer.xml +++ /dev/null @@ -1,52 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> - - <View - android:layout_width="match_parent" - android:layout_height="@dimen/divider_line_thickness" - android:background="@color/call_log_action_divider"/> - - <TextView - android:id="@+id/call_detail_action_copy" - style="@style/CallDetailActionItemStyle" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:drawableStart="@drawable/ic_call_detail_content_copy" - android:text="@string/action_copy_number_text"/> - - <TextView - android:id="@+id/call_detail_action_edit_before_call" - style="@style/CallDetailActionItemStyle" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:drawableStart="@drawable/ic_call_detail_edit" - android:text="@string/action_edit_number_before_call" - android:visibility="gone"/> - - <TextView - android:id="@+id/call_detail_action_report" - style="@style/CallDetailActionItemStyle" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:drawableStart="@drawable/ic_call_detail_report" - android:text="@string/action_report_number" - android:visibility="gone"/> - -</LinearLayout> diff --git a/java/com/android/dialer/app/res/layout/call_detail_header.xml b/java/com/android/dialer/app/res/layout/call_detail_header.xml deleted file mode 100644 index fd85f0af1..000000000 --- a/java/com/android/dialer/app/res/layout/call_detail_header.xml +++ /dev/null @@ -1,89 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/caller_information" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingTop="@dimen/call_detail_top_margin" - android:paddingBottom="@dimen/call_detail_bottom_margin" - android:paddingStart="@dimen/call_detail_horizontal_margin" - android:background="@color/background_dialer_white" - android:baselineAligned="false" - android:elevation="@dimen/call_detail_elevation" - android:focusable="true" - android:orientation="horizontal"> - - <QuickContactBadge - android:id="@+id/quick_contact_photo" - android:layout_width="@dimen/contact_photo_size" - android:layout_height="@dimen/contact_photo_size" - android:layout_marginTop="3dp" - android:layout_alignParentStart="true" - android:layout_gravity="top" - android:focusable="true"/> - - <LinearLayout - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:layout_marginStart="@dimen/call_detail_horizontal_margin" - android:gravity="center_vertical" - android:orientation="vertical"> - - <TextView - android:id="@+id/caller_name" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="2dp" - android:layout_marginBottom="3dp" - android:includeFontPadding="false" - android:singleLine="true" - android:textColor="?android:textColorPrimary" - android:textSize="@dimen/call_log_primary_text_size"/> - - <TextView - android:id="@+id/caller_number" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="1dp" - android:singleLine="true" - android:textColor="?android:textColorSecondary" - android:textSize="@dimen/call_log_detail_text_size"/> - - <TextView - android:id="@+id/phone_account_label" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:singleLine="true" - android:textColor="?android:textColorSecondary" - android:textSize="@dimen/call_log_detail_text_size" - android:visibility="gone"/> - - </LinearLayout> - - <ImageView - android:id="@+id/call_back_button" - android:layout_width="@dimen/call_log_list_item_primary_action_dimen" - android:layout_height="@dimen/call_log_list_item_primary_action_dimen" - android:layout_marginEnd="4dp" - android:background="?android:attr/selectableItemBackgroundBorderless" - android:contentDescription="@string/description_call_log_call_action" - android:scaleType="center" - android:src="@drawable/ic_call_24dp" - android:tint="@color/call_log_list_item_primary_action_icon_tint" - android:visibility="gone"/> - -</LinearLayout> diff --git a/java/com/android/dialer/app/res/layout/call_detail_history_item.xml b/java/com/android/dialer/app/res/layout/call_detail_history_item.xml index 5958ee81c..0184a42f2 100644 --- a/java/com/android/dialer/app/res/layout/call_detail_history_item.xml +++ b/java/com/android/dialer/app/res/layout/call_detail_history_item.xml @@ -27,9 +27,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal"> - <view + <com.android.dialer.calllogutils.CallTypeIconsView android:id="@+id/call_type_icon" - class="com.android.dialer.app.calllog.CallTypeIconsView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical"/> diff --git a/java/com/android/dialer/app/res/layout/call_log_activity.xml b/java/com/android/dialer/app/res/layout/call_log_activity.xml new file mode 100644 index 000000000..4e2b1887c --- /dev/null +++ b/java/com/android/dialer/app/res/layout/call_log_activity.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/calllog_frame" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + <com.android.contacts.common.list.ViewPagerTabs + android:id="@+id/viewpager_header" + style="@style/DialtactsActionBarTabTextStyle" + android:layout_width="match_parent" + android:layout_height="@dimen/tab_height" + android:layout_gravity="top" + android:elevation="@dimen/tab_elevation" + android:orientation="horizontal" + android:textAllCaps="true"/> + <android.support.v4.view.ViewPager + android:id="@+id/call_log_pager" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1"/> + <RelativeLayout + android:id="@+id/floating_action_button_container" + android:layout_width="0dp" + android:layout_height="0dp"/> +</LinearLayout> diff --git a/java/com/android/dialer/app/res/layout/call_log_list_item.xml b/java/com/android/dialer/app/res/layout/call_log_list_item.xml index c22ac861d..1592aa928 100644 --- a/java/com/android/dialer/app/res/layout/call_log_list_item.xml +++ b/java/com/android/dialer/app/res/layout/call_log_list_item.xml @@ -93,8 +93,7 @@ android:layout_height="wrap_content" android:orientation="horizontal"> - <view - class="com.android.dialer.app.calllog.CallTypeIconsView" + <com.android.dialer.calllogutils.CallTypeIconsView android:id="@+id/call_type_icons" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/java/com/android/dialer/app/res/menu/call_log_options.xml b/java/com/android/dialer/app/res/menu/call_log_options.xml new file mode 100644 index 000000000..e78b72e3c --- /dev/null +++ b/java/com/android/dialer/app/res/menu/call_log_options.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 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. +--> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:id="@+id/delete_all" + android:orderInCategory="1" + android:showAsAction="never" + android:title="@string/call_log_delete_all"/> +</menu> diff --git a/java/com/android/dialer/app/res/menu/dialtacts_options.xml b/java/com/android/dialer/app/res/menu/dialtacts_options.xml index 434aa81d9..25a3e1811 100644 --- a/java/com/android/dialer/app/res/menu/dialtacts_options.xml +++ b/java/com/android/dialer/app/res/menu/dialtacts_options.xml @@ -16,13 +16,17 @@ <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item - android:id="@+id/menu_delete_all" - android:title="@string/call_log_delete_all"/> + android:id="@+id/menu_history" + android:icon="@drawable/ic_menu_history_lt" + android:title="@string/action_menu_call_history_description"/> <item android:id="@+id/menu_clear_frequents" android:title="@string/menu_clear_frequents"/> <item android:id="@+id/menu_call_settings" android:title="@string/dialer_settings_label"/> + <item + android:id="@+id/menu_simulator_submenu" + android:title="@string/simulator_submenu_label"/> </menu> diff --git a/java/com/android/dialer/app/res/values/colors.xml b/java/com/android/dialer/app/res/values/colors.xml index b88e55276..cf6b926be 100644 --- a/java/com/android/dialer/app/res/values/colors.xml +++ b/java/com/android/dialer/app/res/values/colors.xml @@ -16,7 +16,6 @@ <resources> <color name="dialer_red_highlight_color">#ff1744</color> - <color name="dialer_green_highlight_color">#00c853</color> <color name="dialer_button_text_color">#fff</color> <color name="dialer_flat_button_text_color">@color/dialer_theme_color</color> @@ -84,13 +83,6 @@ as call back, play voicemail, etc. --> <color name="call_log_action_text">@color/dialer_theme_color</color> - <!-- Color for missed call icons. --> - <color name="missed_call">#ff2e58</color> - <!-- Color for answered or outgoing call icons. --> - <color name="answered_call">@color/dialer_green_highlight_color</color> - <!-- Color for blocked call icons. --> - <color name="blocked_call">@color/dialer_secondary_text_color</color> - <color name="dialer_dialpad_touch_tint">@color/dialer_theme_color_20pct</color> <color name="floating_action_button_touch_tint">#80ffffff</color> diff --git a/java/com/android/dialer/app/res/values/dimens.xml b/java/com/android/dialer/app/res/values/dimens.xml index f3fd63350..7da29c7a3 100644 --- a/java/com/android/dialer/app/res/values/dimens.xml +++ b/java/com/android/dialer/app/res/values/dimens.xml @@ -28,7 +28,6 @@ <dimen name="call_log_horizontal_margin">8dp</dimen> <dimen name="call_log_call_action_size">32dp</dimen> <dimen name="call_log_call_action_width">54dp</dimen> - <dimen name="call_log_icon_margin">4dp</dimen> <dimen name="call_log_inner_margin">13dp</dimen> <dimen name="call_log_outer_margin">8dp</dimen> <dimen name="call_log_start_margin">8dp</dimen> @@ -68,7 +67,7 @@ <item name="contact_tile_height_to_width_ratio" type="dimen">76%</item> <dimen name="contact_tile_text_side_padding">12dp</dimen> <dimen name="contact_tile_text_bottom_padding">9dp</dimen> - <dimen name="favorites_row_top_padding">2dp</dimen> + <dimen name="favorites_row_top_padding">1dp</dimen> <dimen name="favorites_row_bottom_padding">0dp</dimen> <dimen name="favorites_row_start_padding">1dp</dimen> @@ -143,6 +142,4 @@ <dimen name="blocked_number_search_text_size">14sp</dimen> <dimen name="blocked_number_settings_description_text_size">14sp</dimen> <dimen name="blocked_number_header_height">48dp</dimen> - - <dimen name="call_type_icon_size">12dp</dimen> </resources> diff --git a/java/com/android/dialer/app/res/values/strings.xml b/java/com/android/dialer/app/res/values/strings.xml index 689ee1ba8..66bf70f1a 100644 --- a/java/com/android/dialer/app/res/values/strings.xml +++ b/java/com/android/dialer/app/res/values/strings.xml @@ -55,9 +55,6 @@ <!-- Label for action to unblock a number [CHAR LIMIT=48]--> <string name="action_unblock_number">Unblock number</string> - <!-- Menu item in call details used to remove a call or voicemail from the call log. --> - <string name="call_details_delete">Delete</string> - <!-- Label for action to edit a number before calling it. [CHAR LIMIT=48] --> <string name="action_edit_number_before_call">Edit number before call</string> @@ -94,7 +91,7 @@ <!-- Missed call notification label, used when there are two or more missed calls --> <string name="notification_missedCallsTitle">Missed calls</string> <!-- Missed call notification message used when there are multiple missed calls --> - <string name="notification_missedCallsMsg"><xliff:g id="num_missed_calls">%s</xliff:g> missed calls</string> + <string name="notification_missedCallsMsg"><xliff:g id="num_missed_calls">%d</xliff:g> missed calls</string> <!-- Message for "call back" Action, which is displayed in the missed call notificaiton. The user will be able to call back to the person or the phone number. [CHAR LIMIT=18] --> @@ -251,15 +248,13 @@ <!-- Label for the dialer app setting page [CHAR LIMIT=30]--> <string name="dialer_settings_label">Settings</string> + <!-- Label for the simulator submenu. This is used to show actions that are useful for development + and testing. [CHAR LIMIT=30]--> + <string name="simulator_submenu_label">Simulator</string> + <!-- Menu item to display all contacts [CHAR LIMIT=30] --> <string name="menu_allContacts">All contacts</string> - <!-- Title bar for call detail screen --> - <string name="callDetailTitle">Call details</string> - - <!-- Toast for call detail screen when couldn't read the requested details --> - <string name="toast_call_detail_error">Details not available</string> - <!-- Item label: jump to the in-call DTMF dialpad. (Part of a list of options shown in the dialer when another call is already in progress.) --> @@ -275,52 +270,6 @@ is already in progress.) --> <string name="dialer_addAnotherCall">Add call</string> - <!-- Title for incoming call type. [CHAR LIMIT=40] --> - <string name="type_incoming">Incoming call</string> - - <!-- Title for incoming call which was transferred to another device. [CHAR LIMIT=60] --> - <string name="type_incoming_pulled">Incoming call transferred to another device</string> - - <!-- Title for outgoing call type. [CHAR LIMIT=40] --> - <string name="type_outgoing">Outgoing call</string> - - <!-- Title for outgoing call which was transferred to another device. [CHAR LIMIT=60] --> - <string name="type_outgoing_pulled">Outgoing call transferred to another device</string> - - <!-- Title for missed call type. [CHAR LIMIT=40] --> - <string name="type_missed">Missed call</string> - - <!-- Title for incoming video call in call details screen [CHAR LIMIT=60] --> - <string name="type_incoming_video">Incoming video call</string> - - <!-- Title for incoming video call in call details screen which was transferred to another device. - [CHAR LIMIT=60] --> - <string name="type_incoming_video_pulled">Incoming video call transferred to another device</string> - - <!-- Title for outgoing video call in call details screen [CHAR LIMIT=60] --> - <string name="type_outgoing_video">Outgoing video call</string> - - <!-- Title for outgoing video call in call details screen which was transferred to another device. - [CHAR LIMIT=60] --> - <string name="type_outgoing_video_pulled">Outgoing video call transferred to another device</string> - - <!-- Title for missed video call in call details screen [CHAR LIMIT=60] --> - <string name="type_missed_video">Missed video call</string> - - <!-- Title for voicemail details screen --> - <string name="type_voicemail">Voicemail</string> - - <!-- Title for rejected call type. [CHAR LIMIT=40] --> - <string name="type_rejected">Declined call</string> - - <!-- Title for blocked call type. [CHAR LIMIT=40] --> - <string name="type_blocked">Blocked call</string> - - <!-- Title for "answered elsewhere" call type. This will happen if a call was ringing - simultaneously on multiple devices, and the user answered it on a device other than the - current device. [CHAR LIMIT=60] --> - <string name="type_answered_elsewhere">Call answered on another device</string> - <!-- Description for incoming calls going to voice mail vs. not --> <string name="actionIncomingCall">Incoming calls</string> @@ -623,28 +572,10 @@ [CHAR LIMIT=NONE] --> <string name="description_outgoing_call">Call to <xliff:g example="John Smith" id="nameOrNumber">^1</xliff:g>, <xliff:g example="Mobile" id="typeOrLocation">^2</xliff:g>, <xliff:g example="2 min ago" id="timeOfCall">^3</xliff:g>, <xliff:g example="on SIM 1" id="phoneAccount">^4</xliff:g>.</string> - <!-- String describing the phone account the call was made on or to. This string will be used - in description_incoming_missed_call, description_incoming_answered_call, and - description_outgoing_call. - Note: AccessibilityServices uses this attribute to announce what the view represents. - [CHAR LIMIT=NONE] --> - <string name="description_phone_account">on <xliff:g example="SIM 1" id="phoneAccount">^1</xliff:g></string> - - <!-- String describing the secondary line number the call was received via. - Note: AccessibilityServices use this attribute to announce what the view represents. - [CHAR LIMIT=NONE]--> - <string name="description_via_number">via <xliff:g example="(555) 555-5555" id="number">%1$s</xliff:g></string> - <!-- TextView text item showing the secondary line number the call was received via. [CHAR LIMIT=NONE]--> <string name="call_log_via_number">via <xliff:g example="(555) 555-5555" id="number">%1$s</xliff:g></string> - <!-- String describing the PhoneAccount and via number that a call was received on, if both are - visible. - Note: AccessibilityServices use this attribute to announce what the view represents. - [CHAR LIMIT=NONE]--> - <string name="description_via_number_phone_account">on <xliff:g example="SIM 1" id="phoneAccount">%1$s</xliff:g>, via <xliff:g example="(555) 555-5555" id="number">%2$s</xliff:g></string> - <!-- The order of the PhoneAccount and via number that a call was received on, if both are visible. [CHAR LIMIT=NONE]--> @@ -827,6 +758,9 @@ <!-- Label for the blocked numbers settings section [CHAR LIMIT=30] --> <string name="manage_blocked_numbers_label">Call blocking</string> + <!-- Label for the voicemail settings section [CHAR LIMIT=30] --> + <string name="voicemail_settings_label">Voicemail</string> + <!-- Label for a section describing that call blocking is temporarily disabled because an emergency call was made. [CHAR LIMIT=50] --> <string name="blocked_numbers_disabled_emergency_header_label"> @@ -955,6 +889,6 @@ <string name="spam_number_call_log_label">Spam</string> <!-- Shown as a message that notifies the user enriched calling isn't working --> - <string name="call_composer_connection_failed"><xliff:g id="feature">%1$s</xliff:g> unavailable right now</string> + <string name="call_composer_connection_failed"><xliff:g id="name">%1$s</xliff:g> is offline and can\'t be reached</string> </resources> diff --git a/java/com/android/dialer/app/res/values/styles.xml b/java/com/android/dialer/app/res/values/styles.xml index ac4422ba2..24521ddaf 100644 --- a/java/com/android/dialer/app/res/values/styles.xml +++ b/java/com/android/dialer/app/res/values/styles.xml @@ -111,11 +111,6 @@ <item name="android:fastScrollTrackDrawable">@null</item> </style> - <style name="CallDetailActivityTheme" parent="DialtactsThemeWithoutActionBarOverlay"> - <item name="android:windowBackground">@color/background_dialer_results</item> - <item name="android:actionOverflowButtonStyle">@style/DialtactsActionBarOverflowWhite</item> - </style> - <style name="CallDetailActionItemStyle"> <item name="android:foreground">?android:attr/selectableItemBackground</item> <item name="android:clickable">true</item> diff --git a/java/com/android/dialer/app/settings/DialerSettingsActivity.java b/java/com/android/dialer/app/settings/DialerSettingsActivity.java index b04674013..bbf1cfae5 100644 --- a/java/com/android/dialer/app/settings/DialerSettingsActivity.java +++ b/java/com/android/dialer/app/settings/DialerSettingsActivity.java @@ -33,8 +33,11 @@ import com.android.dialer.app.R; import com.android.dialer.blocking.FilteredNumberCompat; import com.android.dialer.compat.CompatUtils; import com.android.dialer.proguard.UsedByReflection; +import com.android.voicemail.VoicemailComponent; import java.util.List; +/** Activity for dialer settings. */ +@SuppressWarnings("FragmentInjection") // Activity not exported @UsedByReflection(value = "AndroidManifest-app.xml") public class DialerSettingsActivity extends AppCompatPreferenceActivity { @@ -115,6 +118,16 @@ public class DialerSettingsActivity extends AppCompatPreferenceActivity { target.add(blockedCallsHeader); migrationStatusOnBuildHeaders = FilteredNumberCompat.hasMigratedToNewBlocking(this); } + + String voicemailSettingsFragment = + VoicemailComponent.get(this).getVoicemailClient().getSettingsFragment(); + if (isPrimaryUser && voicemailSettingsFragment != null) { + Header voicemailSettings = new Header(); + voicemailSettings.titleRes = R.string.voicemail_settings_label; + voicemailSettings.fragment = voicemailSettingsFragment; + target.add(voicemailSettings); + } + if (isPrimaryUser && (TelephonyManagerCompat.isTtyModeSupported(telephonyManager) || TelephonyManagerCompat.isHearingAidCompatibilitySupported(telephonyManager))) { diff --git a/java/com/android/dialer/app/voicemail/VoicemailPlaybackLayout.java b/java/com/android/dialer/app/voicemail/VoicemailPlaybackLayout.java index fc6a37608..f40ed2794 100644 --- a/java/com/android/dialer/app/voicemail/VoicemailPlaybackLayout.java +++ b/java/com/android/dialer/app/voicemail/VoicemailPlaybackLayout.java @@ -30,7 +30,6 @@ import android.widget.LinearLayout; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView; -import com.android.dialer.app.PhoneCallDetails; import com.android.dialer.app.R; import com.android.dialer.app.calllog.CallLogAsyncTaskUtil; import com.android.dialer.app.calllog.CallLogListItemViewHolder; @@ -347,16 +346,10 @@ public class VoicemailPlaybackLayout extends LinearLayout } @Override - public void onDeleteCall() {} - - @Override public void onDeleteVoicemail() { mPresenter.onVoicemailDeletedInDatabase(); } - @Override - public void onGetCallDetails(PhoneCallDetails[] details) {} - private String getString(int resId) { return mContext.getString(resId); } diff --git a/java/com/android/dialer/app/voicemail/VoicemailPlaybackPresenter.java b/java/com/android/dialer/app/voicemail/VoicemailPlaybackPresenter.java index 657022291..994160ff9 100644 --- a/java/com/android/dialer/app/voicemail/VoicemailPlaybackPresenter.java +++ b/java/com/android/dialer/app/voicemail/VoicemailPlaybackPresenter.java @@ -39,6 +39,7 @@ import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.support.v4.content.FileProvider; import android.text.TextUtils; +import android.view.View; import android.view.WindowManager.LayoutParams; import android.webkit.MimeTypeMap; import com.android.common.io.MoreCloseables; @@ -47,8 +48,11 @@ import com.android.dialer.app.calllog.CallLogListItemViewHolder; import com.android.dialer.common.Assert; import com.android.dialer.common.AsyncTaskExecutor; import com.android.dialer.common.AsyncTaskExecutors; +import com.android.dialer.common.ConfigProviderBindings; import com.android.dialer.common.LogUtil; import com.android.dialer.constants.Constants; +import com.android.dialer.logging.Logger; +import com.android.dialer.logging.nano.DialerImpression; import com.android.dialer.phonenumbercache.CallLogQuery; import com.google.common.io.ByteStreams; import java.io.File; @@ -71,9 +75,9 @@ import javax.annotation.concurrent.ThreadSafe; * assumptions about the behaviors and lifecycle of the call log, in particular in the {@link * CallLogFragment} and {@link CallLogAdapter}. * - * <p>This controls a single {@link com.android.dialer.voicemail.VoicemailPlaybackLayout}. A single - * instance can be reused for different such layouts, using {@link #setPlaybackView}. This is to - * facilitate reuse across different voicemail call log entries. + * <p>This controls a single {@link com.android.dialer.app.voicemail.VoicemailPlaybackLayout}. A + * single instance can be reused for different such layouts, using {@link #setPlaybackView}. This is + * to facilitate reuse across different voicemail call log entries. * * <p>This class is not thread safe. The thread policy for this class is thread-confinement, all * calls into this class from outside must be done from the main UI thread. @@ -103,6 +107,8 @@ public class VoicemailPlaybackPresenter private static final String IS_SPEAKERPHONE_ON_KEY = VoicemailPlaybackPresenter.class.getName() + ".IS_SPEAKER_PHONE_ON"; private static final String VOICEMAIL_SHARE_FILE_NAME_DATE_FORMAT = "MM-dd-yy_hhmmaa"; + private static final String CONFIG_SHARE_VOICEMAIL_ALLOWED = "share_voicemail_allowed"; + private static VoicemailPlaybackPresenter sInstance; private static ScheduledExecutorService mScheduledExecutorService; /** @@ -138,6 +144,7 @@ public class VoicemailPlaybackPresenter private PowerManager.WakeLock mProximityWakeLock; private VoicemailAudioManager mVoicemailAudioManager; private OnVoicemailDeletedListener mOnVoicemailDeletedListener; + private View shareVoicemailButtonView; /** Initialize variables which are activity-independent and state-independent. */ protected VoicemailPlaybackPresenter(Activity activity) { @@ -222,11 +229,17 @@ public class VoicemailPlaybackPresenter /** Specify the view which this presenter controls and the voicemail to prepare to play. */ public void setPlaybackView( - PlaybackView view, long rowId, Uri voicemailUri, final boolean startPlayingImmediately) { + PlaybackView view, + long rowId, + Uri voicemailUri, + final boolean startPlayingImmediately, + View shareVoicemailButtonView) { mRowId = rowId; mView = view; mView.setPresenter(this, voicemailUri); mView.onSpeakerphoneOn(mIsSpeakerphoneOn); + this.shareVoicemailButtonView = shareVoicemailButtonView; + showShareVoicemailButton(false); // Handles cases where the same entry is binded again when scrolling in list, or where // the MediaPlayer was retained after an orientation change. @@ -236,6 +249,7 @@ public class VoicemailPlaybackPresenter // media player. mPosition = mMediaPlayer.getCurrentPosition(); onPrepared(mMediaPlayer); + showShareVoicemailButton(true); } else { if (!voicemailUri.equals(mVoicemailUri)) { mVoicemailUri = voicemailUri; @@ -247,19 +261,17 @@ public class VoicemailPlaybackPresenter * it if the content is not available. */ checkForContent( - new OnContentCheckedListener() { - @Override - public void onContentChecked(boolean hasContent) { - if (hasContent) { - prepareContent(); - } else { - if (startPlayingImmediately) { - requestContent(PLAYBACK_REQUEST); - } - if (mView != null) { - mView.resetSeekBar(); - mView.setClipPosition(0, mDuration.get()); - } + hasContent -> { + if (hasContent) { + showShareVoicemailButton(true); + prepareContent(); + } else { + if (startPlayingImmediately) { + requestContent(PLAYBACK_REQUEST); + } + if (mView != null) { + mView.resetSeekBar(); + mView.setClipPosition(0, mDuration.get()); } } }); @@ -547,6 +559,7 @@ public class VoicemailPlaybackPresenter mPosition = 0; mIsPlaying = false; + showShareVoicemailButton(false); } /** After done playing the voicemail clip, reset the clip position to the start. */ @@ -600,18 +613,16 @@ public class VoicemailPlaybackPresenter * timeout, but succeeded. */ checkForContent( - new OnContentCheckedListener() { - @Override - public void onContentChecked(boolean hasContent) { - if (!hasContent) { - // No local content, download from server. Queue playing if the request was - // issued, - mIsPlaying = requestContent(PLAYBACK_REQUEST); - } else { - // Queue playing once the media play loaded the content. - mIsPlaying = true; - prepareContent(); - } + hasContent -> { + if (!hasContent) { + // No local content, download from server. Queue playing if the request was + // issued, + mIsPlaying = requestContent(PLAYBACK_REQUEST); + } else { + showShareVoicemailButton(true); + // Queue playing once the media play loaded the content. + mIsPlaying = true; + prepareContent(); } }); return; @@ -813,6 +824,20 @@ public class VoicemailPlaybackPresenter sInstance = null; } + private void showShareVoicemailButton(boolean show) { + if (isShareVoicemailAllowed(mContext) && shareVoicemailButtonView != null) { + if (show) { + Logger.get(mContext).logImpression(DialerImpression.Type.VVM_SHARE_VISIBLE); + } + LogUtil.d("VoicemailPlaybackPresenter.showShareVoicemailButton", "show: %b", show); + shareVoicemailButtonView.setVisibility(show ? View.VISIBLE : View.GONE); + } + } + + private static boolean isShareVoicemailAllowed(Context context) { + return ConfigProviderBindings.get(context).getBoolean(CONFIG_SHARE_VOICEMAIL_ALLOWED, true); + } + /** * Share voicemail to be opened by user selected apps. This method will collect information, copy * voicemail to a temporary file in background and launch a chooser intent to share it. @@ -1041,6 +1066,7 @@ public class VoicemailPlaybackPresenter public void onPostExecute(Boolean hasContent) { if (hasContent && mContext != null && mIsWaitingForResult.getAndSet(false)) { mContext.getContentResolver().unregisterContentObserver(FetchResultHandler.this); + showShareVoicemailButton(true); prepareContent(); } } diff --git a/java/com/android/dialer/app/voicemail/error/OmtpVoicemailMessageCreator.java b/java/com/android/dialer/app/voicemail/error/OmtpVoicemailMessageCreator.java index e36406d17..190426e6e 100644 --- a/java/com/android/dialer/app/voicemail/error/OmtpVoicemailMessageCreator.java +++ b/java/com/android/dialer/app/voicemail/error/OmtpVoicemailMessageCreator.java @@ -17,10 +17,18 @@ package com.android.dialer.app.voicemail.error; import android.content.Context; +import android.preference.PreferenceManager; import android.provider.VoicemailContract.Status; import android.support.annotation.Nullable; +import android.telecom.PhoneAccountHandle; import com.android.dialer.app.voicemail.error.VoicemailErrorMessage.Action; +import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; +import com.android.dialer.common.PerAccountSharedPreferences; +import com.android.dialer.logging.Logger; +import com.android.dialer.logging.nano.DialerImpression; +import com.android.voicemail.VoicemailClient; +import com.android.voicemail.VoicemailComponent; import java.util.ArrayList; import java.util.List; @@ -32,14 +40,18 @@ public class OmtpVoicemailMessageCreator { private static final float QUOTA_NEAR_FULL_THRESHOLD = 0.9f; private static final float QUOTA_FULL_THRESHOLD = 0.99f; + protected static final String VOICEMAIL_PROMO_DISMISSED_KEY = + "voicemail_archive_promo_was_dismissed"; + protected static final String VOICEMAIL_PROMO_ALMOST_FULL_DISMISSED_KEY = + "voicemail_archive_almost_full_promo_was_dismissed"; @Nullable - public static VoicemailErrorMessage create(Context context, VoicemailStatus status) { + public static VoicemailErrorMessage create( + Context context, VoicemailStatus status, final VoicemailStatusReader statusReader) { if (Status.CONFIGURATION_STATE_OK == status.configurationState && Status.DATA_CHANNEL_STATE_OK == status.dataChannelState && Status.NOTIFICATION_CHANNEL_STATE_OK == status.notificationChannelState) { - - return checkQuota(context, status); + return checkQuota(context, status, statusReader); } // Initial state when the source is activating. Other error might be written into data and // notification channel during activation. @@ -120,24 +132,98 @@ public class OmtpVoicemailMessageCreator { } @Nullable - private static VoicemailErrorMessage checkQuota(Context context, VoicemailStatus status) { + private static VoicemailErrorMessage checkQuota( + Context context, VoicemailStatus status, VoicemailStatusReader statusReader) { if (status.quotaOccupied != Status.QUOTA_UNAVAILABLE && status.quotaTotal != Status.QUOTA_UNAVAILABLE) { + + PhoneAccountHandle phoneAccountHandle = status.getPhoneAccountHandle(); + + VoicemailClient voicemailClient = VoicemailComponent.get(context).getVoicemailClient(); + + PerAccountSharedPreferences sharedPreferenceForAccount = + new PerAccountSharedPreferences( + context, phoneAccountHandle, PreferenceManager.getDefaultSharedPreferences(context)); + + boolean isVoicemailArchiveEnabled = + VoicemailComponent.get(context) + .getVoicemailClient() + .isVoicemailArchiveEnabled(context, phoneAccountHandle); + if ((float) status.quotaOccupied / (float) status.quotaTotal >= QUOTA_FULL_THRESHOLD) { - return new VoicemailErrorMessage( + return createInboxErrorMessage( + context, + status, + status.getPhoneAccountHandle(), + statusReader, + sharedPreferenceForAccount, + voicemailClient, + isVoicemailArchiveEnabled, + context.getString(R.string.voicemail_error_inbox_full_turn_archive_on_title), + context.getString(R.string.voicemail_error_inbox_full_turn_archive_on_message), context.getString(R.string.voicemail_error_inbox_full_title), - context.getString(R.string.voicemail_error_inbox_full_message)); + context.getString(R.string.voicemail_error_inbox_full_message), + VOICEMAIL_PROMO_DISMISSED_KEY); } if ((float) status.quotaOccupied / (float) status.quotaTotal >= QUOTA_NEAR_FULL_THRESHOLD) { - return new VoicemailErrorMessage( + return createInboxErrorMessage( + context, + status, + status.getPhoneAccountHandle(), + statusReader, + sharedPreferenceForAccount, + voicemailClient, + isVoicemailArchiveEnabled, + context.getString(R.string.voicemail_error_inbox_almost_full_turn_archive_on_title), + context.getString(R.string.voicemail_error_inbox_almost_full_turn_archive_on_message), context.getString(R.string.voicemail_error_inbox_near_full_title), - context.getString(R.string.voicemail_error_inbox_near_full_message)); + context.getString(R.string.voicemail_error_inbox_near_full_message), + VOICEMAIL_PROMO_ALMOST_FULL_DISMISSED_KEY); } } return null; } + private static VoicemailErrorMessage createInboxErrorMessage( + Context context, + VoicemailStatus status, + PhoneAccountHandle phoneAccountHandle, + VoicemailStatusReader statusReader, + PerAccountSharedPreferences sharedPreferenceForAccount, + VoicemailClient voicemailClient, + boolean isVoicemailArchiveEnabled, + String promoTitle, + String promoMessage, + String nonPromoTitle, + String nonPromoMessage, + String preferenceKey) { + + boolean wasPromoDismissed = sharedPreferenceForAccount.getBoolean(preferenceKey, false); + + if (!wasPromoDismissed && !isVoicemailArchiveEnabled) { + logArchiveImpression( + context, + preferenceKey, + DialerImpression.Type.VVM_USER_SHOWN_VM_ALMOST_FULL_PROMO, + DialerImpression.Type.VVM_USER_SHOWN_VM_FULL_PROMO); + return new VoicemailErrorMessage( + promoTitle, + promoMessage, + VoicemailErrorMessage.createDismissTurnArchiveOnAction( + context, statusReader, sharedPreferenceForAccount, preferenceKey), + VoicemailErrorMessage.createTurnArchiveOnAction( + context, status, voicemailClient, phoneAccountHandle, preferenceKey)); + } else { + logArchiveImpression( + context, + preferenceKey, + DialerImpression.Type.VVM_USER_SHOWN_VM_ALMOST_FULL_ERROR_MESSAGE, + DialerImpression.Type.VVM_USER_SHOWN_VM_FULL_ERROR_MESSAGE); + return new VoicemailErrorMessage(nonPromoTitle, nonPromoMessage); + } + } + @Nullable private static VoicemailErrorMessage createNoSignalMessage( Context context, VoicemailStatus status) { @@ -174,4 +260,15 @@ public class OmtpVoicemailMessageCreator { } return new VoicemailErrorMessage(title, description, actions); } + + protected static void logArchiveImpression( + Context context, String preference, int vmAlmostFullImpression, int vmFullImpression) { + if (preference.equals(VOICEMAIL_PROMO_DISMISSED_KEY)) { + Logger.get(context).logImpression(vmAlmostFullImpression); + } else if (preference.equals(VOICEMAIL_PROMO_ALMOST_FULL_DISMISSED_KEY)) { + Logger.get(context).logImpression(vmFullImpression); + } else { + throw Assert.createAssertionFailException("Invalid preference key " + preference); + } + } } diff --git a/java/com/android/dialer/app/voicemail/error/VoicemailErrorMessage.java b/java/com/android/dialer/app/voicemail/error/VoicemailErrorMessage.java index 61572008b..f85d91186 100644 --- a/java/com/android/dialer/app/voicemail/error/VoicemailErrorMessage.java +++ b/java/com/android/dialer/app/voicemail/error/VoicemailErrorMessage.java @@ -22,12 +22,15 @@ import android.provider.Settings; import android.provider.VoicemailContract; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.telecom.PhoneAccountHandle; import android.telephony.TelephonyManager; import android.view.View; import android.view.View.OnClickListener; +import com.android.dialer.common.PerAccountSharedPreferences; import com.android.dialer.logging.Logger; import com.android.dialer.logging.nano.DialerImpression; import com.android.dialer.util.CallUtil; +import com.android.voicemail.VoicemailClient; import java.util.Arrays; import java.util.List; @@ -175,4 +178,52 @@ public class VoicemailErrorMessage { } }); } + + @NonNull + public static Action createTurnArchiveOnAction( + final Context context, + final VoicemailStatus status, + VoicemailClient voicemailClient, + PhoneAccountHandle phoneAccountHandle, + String preference) { + return new Action( + context.getString(R.string.voicemail_action_turn_archive_on), + new OnClickListener() { + @Override + public void onClick(View v) { + OmtpVoicemailMessageCreator.logArchiveImpression( + context, + preference, + DialerImpression.Type.VVM_USER_ENABLED_ARCHIVE_FROM_VM_FULL_PROMO, + DialerImpression.Type.VVM_USER_ENABLED_ARCHIVE_FROM_VM_ALMOST_FULL_PROMO); + + voicemailClient.setVoicemailArchiveEnabled(context, phoneAccountHandle, true); + Intent intent = new Intent(VoicemailContract.ACTION_SYNC_VOICEMAIL); + intent.setPackage(status.sourcePackage); + context.sendBroadcast(intent); + } + }); + } + + @NonNull + public static Action createDismissTurnArchiveOnAction( + final Context context, + VoicemailStatusReader statusReader, + PerAccountSharedPreferences sharedPreferenceForAccount, + String preferenceKeyToUpdate) { + return new Action( + context.getString(R.string.voicemail_action_dimiss), + new OnClickListener() { + @Override + public void onClick(View v) { + OmtpVoicemailMessageCreator.logArchiveImpression( + context, + preferenceKeyToUpdate, + DialerImpression.Type.VVM_USER_DISMISSED_VM_FULL_PROMO, + DialerImpression.Type.VVM_USER_DISMISSED_VM_ALMOST_FULL_PROMO); + sharedPreferenceForAccount.edit().putBoolean(preferenceKeyToUpdate, true); + statusReader.refresh(); + } + }); + } } diff --git a/java/com/android/dialer/app/voicemail/error/VoicemailErrorMessageCreator.java b/java/com/android/dialer/app/voicemail/error/VoicemailErrorMessageCreator.java index 5ebef801d..7dc18f043 100644 --- a/java/com/android/dialer/app/voicemail/error/VoicemailErrorMessageCreator.java +++ b/java/com/android/dialer/app/voicemail/error/VoicemailErrorMessageCreator.java @@ -39,7 +39,7 @@ public class VoicemailErrorMessageCreator { case Vvm3VoicemailMessageCreator.VVM_TYPE_VVM3: return Vvm3VoicemailMessageCreator.create(context, status, statusReader); default: - return OmtpVoicemailMessageCreator.create(context, status); + return OmtpVoicemailMessageCreator.create(context, status, statusReader); } } } diff --git a/java/com/android/dialer/app/voicemail/error/VoicemailStatus.java b/java/com/android/dialer/app/voicemail/error/VoicemailStatus.java index a09941de2..c429d6dcc 100644 --- a/java/com/android/dialer/app/voicemail/error/VoicemailStatus.java +++ b/java/com/android/dialer/app/voicemail/error/VoicemailStatus.java @@ -16,6 +16,7 @@ package com.android.dialer.app.voicemail.error; +import android.content.ComponentName; import android.content.Context; import android.database.Cursor; import android.net.Uri; @@ -25,6 +26,7 @@ import android.provider.Settings; import android.provider.Settings.Global; import android.provider.VoicemailContract.Status; import android.support.annotation.Nullable; +import android.telecom.PhoneAccountHandle; import android.telephony.TelephonyManager; import com.android.dialer.database.VoicemailStatusQuery; @@ -257,4 +259,9 @@ public class VoicemailStatus { } return cursor.getString(index); } + + public PhoneAccountHandle getPhoneAccountHandle() { + return new PhoneAccountHandle( + ComponentName.unflattenFromString(phoneAccountComponentName), phoneAccountId); + } } diff --git a/java/com/android/dialer/app/voicemail/error/Vvm3VoicemailMessageCreator.java b/java/com/android/dialer/app/voicemail/error/Vvm3VoicemailMessageCreator.java index 6e9405cbf..356131bb3 100644 --- a/java/com/android/dialer/app/voicemail/error/Vvm3VoicemailMessageCreator.java +++ b/java/com/android/dialer/app/voicemail/error/Vvm3VoicemailMessageCreator.java @@ -269,7 +269,7 @@ public class Vvm3VoicemailMessageCreator { VoicemailErrorMessage.createSetPinAction(context)); } - return OmtpVoicemailMessageCreator.create(context, status); + return OmtpVoicemailMessageCreator.create(context, status, statusReader); } @NonNull diff --git a/java/com/android/dialer/app/voicemail/error/res/layout/voicemai_error_message_fragment.xml b/java/com/android/dialer/app/voicemail/error/res/layout/voicemai_error_message_fragment.xml index 0dfb1c2fd..4a40857a0 100644 --- a/java/com/android/dialer/app/voicemail/error/res/layout/voicemai_error_message_fragment.xml +++ b/java/com/android/dialer/app/voicemail/error/res/layout/voicemai_error_message_fragment.xml @@ -48,7 +48,6 @@ <TextView android:id="@+id/error_card_details" - android:autoLink="web" android:layout_width="wrap_content" android:layout_height="wrap_content" android:lineSpacingExtra="@dimen/alert_line_spacing" diff --git a/java/com/android/dialer/app/voicemail/error/res/layout/voicemail_tos_fragment.xml b/java/com/android/dialer/app/voicemail/error/res/layout/voicemail_tos_fragment.xml index 2b9d17328..c193eaa47 100644 --- a/java/com/android/dialer/app/voicemail/error/res/layout/voicemail_tos_fragment.xml +++ b/java/com/android/dialer/app/voicemail/error/res/layout/voicemail_tos_fragment.xml @@ -31,7 +31,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingBottom="16dp" - android:autoLink="web" android:text="@string/verizon_terms_and_conditions_1.1_english" android:textColor="@color/secondary_text_color" android:textSize="@dimen/call_log_detail_text_size"/> diff --git a/java/com/android/dialer/app/voicemail/error/res/values/strings.xml b/java/com/android/dialer/app/voicemail/error/res/values/strings.xml index 1d39b9dcb..94d3dba11 100644 --- a/java/com/android/dialer/app/voicemail/error/res/values/strings.xml +++ b/java/com/android/dialer/app/voicemail/error/res/values/strings.xml @@ -54,6 +54,11 @@ <string name="voicemail_error_inbox_full_title">Can\'t receive new voicemails</string> <string name="voicemail_error_inbox_full_message">Your inbox is full. Try deleting some messages to receive new voicemail.</string> + <string name="voicemail_error_inbox_full_turn_archive_on_title">Turn on extra storage and backup</string> + <string name="voicemail_error_inbox_full_turn_archive_on_message">Your mailbox is full. To free up space, turn on extra storage so Google can manage and backup your voicemail messages.</string> + + <string name="voicemail_error_inbox_almost_full_turn_archive_on_title">Turn on extra storage and backup</string> + <string name="voicemail_error_inbox_almost_full_turn_archive_on_message">Your mailbox is almost full. To free up space, turn on extra storage so Google can manage and backup your voicemail messages.</string> <string name="voicemail_error_pin_not_set_title">Set your voicemail PIN</string> <string name="voicemail_error_pin_not_set_message">You\'ll need a voicemail PIN anytime you call to access your voicemail.</string> @@ -63,6 +68,8 @@ <string name="voicemail_action_turn_off_airplane_mode">Airplane Mode Settings</string> <string name="voicemail_action_set_pin">Set PIN</string> <string name="voicemail_action_retry">Try Again</string> + <string name="voicemail_action_turn_archive_on">Turn on</string> + <string name="voicemail_action_dimiss">No Thanks</string> <string name="voicemail_action_sync">Sync</string> <string name="voicemail_action_call_voicemail">Call Voicemail</string> <string name="voicemail_action_call_customer_support">Call Customer Support</string> diff --git a/java/com/android/dialer/app/widget/ActionBarController.java b/java/com/android/dialer/app/widget/ActionBarController.java index 7fe056c51..d0eb326ab 100644 --- a/java/com/android/dialer/app/widget/ActionBarController.java +++ b/java/com/android/dialer/app/widget/ActionBarController.java @@ -16,12 +16,9 @@ package com.android.dialer.app.widget; import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; import android.os.Bundle; -import android.support.annotation.VisibleForTesting; -import android.util.Log; import com.android.dialer.animation.AnimUtils.AnimationCallback; -import com.android.dialer.app.DialtactsActivity; +import com.android.dialer.common.LogUtil; /** * Controls the various animated properties of the actionBar: showing/hiding, fading/revealing, and @@ -30,8 +27,6 @@ import com.android.dialer.app.DialtactsActivity; */ public class ActionBarController { - public static final boolean DEBUG = DialtactsActivity.DEBUG; - public static final String TAG = "ActionBarController"; private static final String KEY_IS_SLID_UP = "key_actionbar_is_slid_up"; private static final String KEY_IS_FADED_OUT = "key_actionbar_is_faded_out"; private static final String KEY_IS_EXPANDED = "key_actionbar_is_expanded"; @@ -66,9 +61,8 @@ public class ActionBarController { /** Called when the user has tapped on the collapsed search box, to start a new search query. */ public void onSearchBoxTapped() { - if (DEBUG) { - Log.d(TAG, "OnSearchBoxTapped: isInSearchUi " + mActivityUi.isInSearchUi()); - } + LogUtil.d( + "ActionBarController.onSearchBoxTapped", "isInSearchUi " + mActivityUi.isInSearchUi()); if (!mActivityUi.isInSearchUi()) { mSearchBox.expand(true /* animate */, true /* requestFocus */); } @@ -76,16 +70,11 @@ public class ActionBarController { /** Called when search UI has been exited for some reason. */ public void onSearchUiExited() { - if (DEBUG) { - Log.d( - TAG, - "OnSearchUIExited: isExpanded " - + mSearchBox.isExpanded() - + " isFadedOut: " - + mSearchBox.isFadedOut() - + " shouldShowActionBar: " - + mActivityUi.shouldShowActionBar()); - } + LogUtil.d( + "ActionBarController.onSearchUIExited", + "isExpanded: %b, isFadedOut %b", + mSearchBox.isExpanded(), + mSearchBox.isFadedOut()); if (mSearchBox.isExpanded()) { mSearchBox.collapse(true /* animate */); } @@ -93,11 +82,7 @@ public class ActionBarController { mSearchBox.fadeIn(); } - if (mActivityUi.shouldShowActionBar()) { - slideActionBar(false /* slideUp */, false /* animate */); - } else { - slideActionBar(true /* slideUp */, false /* animate */); - } + slideActionBar(false /* slideUp */, false /* animate */); } /** @@ -105,18 +90,13 @@ public class ActionBarController { * state changes have actually occurred. */ public void onDialpadDown() { - if (DEBUG) { - Log.d( - TAG, - "OnDialpadDown: isInSearchUi " - + mActivityUi.isInSearchUi() - + " hasSearchQuery: " - + mActivityUi.hasSearchQuery() - + " isFadedOut: " - + mSearchBox.isFadedOut() - + " isExpanded: " - + mSearchBox.isExpanded()); - } + LogUtil.d( + "ActionBarController.onDialpadDown", + "isInSearchUi: %b, hasSearchQuery: %b, isFadedOut: %b, isExpanded: %b", + mActivityUi.isInSearchUi(), + mActivityUi.hasSearchQuery(), + mSearchBox.isFadedOut(), + mSearchBox.isExpanded()); if (mActivityUi.isInSearchUi()) { if (mActivityUi.hasSearchQuery()) { if (mSearchBox.isFadedOut()) { @@ -137,9 +117,7 @@ public class ActionBarController { * state changes have actually occurred. */ public void onDialpadUp() { - if (DEBUG) { - Log.d(TAG, "OnDialpadUp: isInSearchUi " + mActivityUi.isInSearchUi()); - } + LogUtil.d("ActionBarController.onDialpadUp", "isInSearchUi " + mActivityUi.isInSearchUi()); if (mActivityUi.isInSearchUi()) { slideActionBar(true /* slideUp */, true /* animate */); } else { @@ -149,18 +127,14 @@ public class ActionBarController { } public void slideActionBar(boolean slideUp, boolean animate) { - if (DEBUG) { - Log.d(TAG, "Sliding actionBar - up: " + slideUp + " animate: " + animate); - } + LogUtil.d("ActionBarController.slidingActionBar", "up: %b, animate: %b", slideUp, animate); + if (animate) { ValueAnimator animator = slideUp ? ValueAnimator.ofFloat(0, 1) : ValueAnimator.ofFloat(1, 0); animator.addUpdateListener( - new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - final float value = (float) animation.getAnimatedValue(); - setHideOffset((int) (mActivityUi.getActionBarHeight() * value)); - } + animation -> { + final float value = (float) animation.getAnimatedValue(); + setHideOffset((int) (mActivityUi.getActionBarHeight() * value)); }); animator.start(); } else { @@ -173,20 +147,11 @@ public class ActionBarController { mSearchBox.animate().alpha(alphaValue).start(); } - /** @return The offset the action bar is being translated upwards by */ - public int getHideOffset() { - return mActivityUi.getActionBarHideOffset(); - } - public void setHideOffset(int offset) { mIsActionBarSlidUp = offset >= mActivityUi.getActionBarHeight(); mActivityUi.setActionBarHideOffset(offset); } - public int getActionBarHeight() { - return mActivityUi.getActionBarHeight(); - } - /** Saves the current state of the action bar into a provided {@link Bundle} */ public void saveInstanceState(Bundle outState) { outState.putBoolean(KEY_IS_SLID_UP, mIsActionBarSlidUp); @@ -225,23 +190,14 @@ public class ActionBarController { slideActionBar(mIsActionBarSlidUp /* slideUp */, false /* animate */); } - @VisibleForTesting - public boolean getIsActionBarSlidUp() { - return mIsActionBarSlidUp; - } - public interface ActivityUi { boolean isInSearchUi(); boolean hasSearchQuery(); - boolean shouldShowActionBar(); - int getActionBarHeight(); - int getActionBarHideOffset(); - void setActionBarHideOffset(int offset); } } |