diff options
17 files changed, 868 insertions, 55 deletions
diff --git a/AndroidManifest_cm.xml b/AndroidManifest_cm.xml index de674de1e..9a156105f 100644 --- a/AndroidManifest_cm.xml +++ b/AndroidManifest_cm.xml @@ -28,6 +28,9 @@ <application> + <meta-data android:name="com.cyanogen.ambient.analytics.key" + android:value="6l2dXt9DFioFa1Mfb4eZsM9l87Rl9hp1UL75tO9w"/> + <receiver android:name=".incall.CallMethodStatusReceiver" android:enabled="true" android:exported="true"> @@ -60,5 +63,11 @@ android:noHistory="true" android:theme="@style/Theme.Material.Light.Dialog.NoTitle" /> + <service android:name=".incall.InCallMetricsReceiver" > + <intent-filter> + <action android:name="cyanogen.ambient.core.incall.action.plugin_status_changed"/> + </intent-filter> + </service> + </application> </manifest> diff --git a/src/com/android/dialer/CallDetailActivity.java b/src/com/android/dialer/CallDetailActivity.java index 9109828cc..48854bec6 100644 --- a/src/com/android/dialer/CallDetailActivity.java +++ b/src/com/android/dialer/CallDetailActivity.java @@ -275,7 +275,8 @@ public class CallDetailActivity extends Activity return; } } - mContext.startActivity(IntentUtil.getCallIntent(mNumber)); + mContext.startActivity(IntentUtil.getCallIntent(mNumber, + OriginCodes.CALL_LOG_CALL)); } }); diff --git a/src/com/android/dialer/DialerApplication.java b/src/com/android/dialer/DialerApplication.java index 325320ea6..3b5bfb6b9 100644 --- a/src/com/android/dialer/DialerApplication.java +++ b/src/com/android/dialer/DialerApplication.java @@ -22,6 +22,7 @@ import android.os.Trace; import com.android.contacts.common.extensions.ExtensionsFactory; import com.android.contacts.commonbind.analytics.AnalyticsUtil; import com.android.dialer.discovery.WifiCallStatusNudgeListener; +import com.android.dialer.incall.InCallMetricsHelper; import com.android.phone.common.incall.CallMethodHelper; import com.android.dialer.util.MetricsHelper; @@ -30,8 +31,6 @@ public class DialerApplication extends Application { private static final String TAG = "DialerApplication"; - - @Override public void onCreate() { Trace.beginSection(TAG + " onCreate"); @@ -48,6 +47,7 @@ public class DialerApplication extends Application { CallMethodHelper.init(this); MetricsHelper.init(this); WifiCallStatusNudgeListener.init(this); + InCallMetricsHelper.init(this); Trace.endSection(); } diff --git a/src/com/android/dialer/DialtactsActivity.java b/src/com/android/dialer/DialtactsActivity.java index e76653eb6..90f901ce8 100644 --- a/src/com/android/dialer/DialtactsActivity.java +++ b/src/com/android/dialer/DialtactsActivity.java @@ -1509,8 +1509,8 @@ public class DialtactsActivity extends TransactionSafeActivity implements View.O PhoneAccountHandle handle = CallMethodInfo.getPhoneAccountHandleFromCallMethodInfo(this, cmi, phoneNumber); Intent intent = isVideoCall ? - IntentUtil.getVideoCallIntent(phoneNumber, getCallOrigin()) : - IntentUtil.getCallIntent(phoneNumber, getCallOrigin(), handle); + IntentUtil.getVideoCallIntent(phoneNumber, OriginCodes.CONTACT_SEARCH) : + IntentUtil.getCallIntent(phoneNumber, OriginCodes.CONTACT_SEARCH, handle); DialerUtils.startActivityWithErrorToast(this, intent); } mClearSearchOnPause = true; diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java index d3f314e90..7ca61204d 100644 --- a/src/com/android/dialer/calllog/CallLogAdapter.java +++ b/src/com/android/dialer/calllog/CallLogAdapter.java @@ -56,6 +56,7 @@ import com.android.dialer.util.DialerUtils; import com.android.dialer.util.PhoneNumberUtil; import com.android.dialer.voicemail.VoicemailPlaybackPresenter; +import com.cyanogen.ambient.incall.extension.OriginCodes; import com.google.common.annotations.VisibleForTesting; import java.util.HashMap; @@ -246,7 +247,8 @@ public class CallLogAdapter extends GroupingListAdapter final Intent intent = new Intent(Intent.ACTION_DIAL, CallUtil.getCallUri(vh.number)); intent.setClass(mContext, DialtactsActivity.class); - DialerUtils.startActivityWithErrorToast(mContext, intent); + DialerUtils.startActivityWithErrorToast(mContext, intent, OriginCodes + .CALL_LOG_CALL); return true; } }); diff --git a/src/com/android/dialer/calllog/CallLogListItemViewHolder.java b/src/com/android/dialer/calllog/CallLogListItemViewHolder.java index b36bf9bfb..34e31288f 100644 --- a/src/com/android/dialer/calllog/CallLogListItemViewHolder.java +++ b/src/com/android/dialer/calllog/CallLogListItemViewHolder.java @@ -339,7 +339,8 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder primaryActionButtonView.setTag("phone"); } else { primaryActionButtonView.setTag( - IntentProvider.getReturnCallIntentProvider(number)); + IntentProvider.getReturnCallIntentProvider(number, + OriginCodes.CALL_LOG_PRIMARY)); } } @@ -369,7 +370,8 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder if (!TextUtils.isEmpty(voicemailUri) && canPlaceCallToNumber || cmi != null && cmi.mIsInCallProvider) { - callButtonView.setTag(IntentProvider.getReturnCallIntentProvider(number)); + callButtonView.setTag(IntentProvider.getReturnCallIntentProvider(number, + OriginCodes.CALL_LOG_ACTION_ONE)); if (cmi != null && cmi.mIsInCallProvider) { ((TextView) callButtonView.findViewById(R.id.call_action_text)) .setText(mContext.getString(R.string.provider_voice_call, cmi.mName)); @@ -395,7 +397,8 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder videoCallButtonView.setTag("video"); videoCallButtonView.setVisibility(View.VISIBLE); } else { - videoCallButtonView.setTag(IntentProvider.getReturnVideoCallIntentProvider(number)); + videoCallButtonView.setTag(IntentProvider.getReturnVideoCallIntentProvider + (number, OriginCodes.CALL_LOG_ACTION_TWO)); videoCallButtonView.setVisibility(View.VISIBLE); } } else { @@ -566,13 +569,13 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder String callProviderActionName = (String) tag; switch (callProviderActionName) { case "video": - cmi.placeCall(OriginCodes.CALL_LOG_CALL, number, mContext, true); + cmi.placeCall(OriginCodes.CALL_LOG_ACTION_TWO, number, mContext, true); break; case "messaging": // TODO: implement way to start incall message thread break; case "phone": - cmi.placeCall(OriginCodes.CALL_LOG_CALL, number, mContext); + cmi.placeCall(OriginCodes.CALL_LOG_ACTION_ONE, number, mContext); break; default: // Unsupported thing, we shouldn't get here @@ -585,7 +588,8 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder final Intent intent = intentProvider.getIntent(mContext); // See IntentProvider.getCallDetailIntentProvider() for why this may be null. if (intent != null) { - DialerUtils.startActivityWithErrorToast(mContext, intent); + DialerUtils.startActivityWithErrorToast(mContext, intent, + OriginCodes.CALL_LOG_CALL); } } } diff --git a/src/com/android/dialer/calllog/IntentProvider.java b/src/com/android/dialer/calllog/IntentProvider.java index a11d00bc2..5120d0f55 100644 --- a/src/com/android/dialer/calllog/IntentProvider.java +++ b/src/com/android/dialer/calllog/IntentProvider.java @@ -46,30 +46,31 @@ public abstract class IntentProvider { public abstract Intent getIntent(Context context); - public static IntentProvider getReturnCallIntentProvider(final String number) { - return getReturnCallIntentProvider(number, null); + public static IntentProvider getReturnCallIntentProvider(final String number, String origin) { + return getReturnCallIntentProvider(number, null, origin); } public static IntentProvider getReturnCallIntentProvider(final String number, - final PhoneAccountHandle accountHandle) { + final PhoneAccountHandle accountHandle, final String origin) { return new IntentProvider() { @Override public Intent getIntent(Context context) { - return IntentUtil.getCallIntent(number, accountHandle); + return IntentUtil.getCallIntent(number, accountHandle, origin); } }; } - public static IntentProvider getReturnVideoCallIntentProvider(final String number) { - return getReturnVideoCallIntentProvider(number, null); + public static IntentProvider getReturnVideoCallIntentProvider(final String number, + String origin) { + return getReturnVideoCallIntentProvider(number, null, origin); } public static IntentProvider getReturnVideoCallIntentProvider(final String number, - final PhoneAccountHandle accountHandle) { + final PhoneAccountHandle accountHandle, final String origin) { return new IntentProvider() { @Override public Intent getIntent(Context context) { - return IntentUtil.getVideoCallIntent(number, accountHandle); + return IntentUtil.getVideoCallIntent(number, origin, accountHandle); } }; } diff --git a/src/com/android/dialer/dialpad/DialpadFragment.java b/src/com/android/dialer/dialpad/DialpadFragment.java index 399564e71..b2360098c 100644 --- a/src/com/android/dialer/dialpad/DialpadFragment.java +++ b/src/com/android/dialer/dialpad/DialpadFragment.java @@ -1267,7 +1267,7 @@ public class DialpadFragment extends Fragment // Clear the digits just in case. clearDialpad(); } else { - startCall(number); + startCall(number, OriginCodes.DIALPAD_DIRECT_DIAL); hideAndClearDialpad(false); } } @@ -1884,8 +1884,7 @@ public class DialpadFragment extends Fragment if (phoneNumber == null) { showNoSpeedNumberDialog(number); } else { - startCall(phoneNumber); - getActivity().finish(); + startCall(phoneNumber, OriginCodes.SPEED_DIAL); } } @@ -1906,11 +1905,10 @@ public class DialpadFragment extends Fragment .show(); } - private void startCall(String number) { + private void startCall(String number, String origin) { if (mCurrentCallMethodInfo != null && mCurrentCallMethodInfo.mIsInCallProvider && !PhoneNumberUtils.isEmergencyNumber(number)) { - mCurrentCallMethodInfo.placeCall(OriginCodes.DIALPAD_DIRECT_DIAL, - number, getActivity(), false, true); + mCurrentCallMethodInfo.placeCall(origin, number, getActivity(), false, true); } else { // If no sim is selected, or emergency callmethod selected, or number is // an emergency number, phone account handle should be null, and will use the @@ -1924,7 +1922,7 @@ public class DialpadFragment extends Fragment ((DialtactsActivity) getActivity()) .getCallOrigin() : null), handle); - DialerUtils.startActivityWithErrorToast(getActivity(), intent); + DialerUtils.startActivityWithErrorToast(getActivity(), intent, origin); } } } diff --git a/src/com/android/dialer/discovery/DiscoveryEventHandler.java b/src/com/android/dialer/discovery/DiscoveryEventHandler.java index d16442065..29b9f1d77 100644 --- a/src/com/android/dialer/discovery/DiscoveryEventHandler.java +++ b/src/com/android/dialer/discovery/DiscoveryEventHandler.java @@ -1,7 +1,9 @@ package com.android.dialer.discovery; +import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Resources; @@ -28,6 +30,8 @@ import com.cyanogen.ambient.incall.results.InCallProviderInfoResult; import com.cyanogen.ambient.incall.results.PluginStatusResult; import com.cyanogen.ambient.plugin.PluginStatus; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -139,6 +143,31 @@ public class DiscoveryEventHandler { nn.addButton(button); } + Intent intent = new Intent(context, DiscoverySignalReceiver.class); + intent.setAction(DiscoverySignalReceiver.DISCOVERY_NUDGE_SHOWN); + + String nudgeID; + try { + MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); + messageDigest.update(b.getString(NudgeKey.NOTIFICATION_PARAM_BODY) + .getBytes()); + nudgeID = new String(messageDigest.digest()); + } catch (NoSuchAlgorithmException e) { + Log.e(TAG, "No Algo, defaulting to unknown", e); + nudgeID = "unkown"; + } + intent.putExtra(DiscoverySignalReceiver.NUDGE_ID, nudgeID); + + intent.putExtra(DiscoverySignalReceiver.NUDGE_KEY, key); + + intent.putExtra(DiscoverySignalReceiver.NUDGE_COMPONENT, + component.flattenToShortString()); + + PendingIntent pendingIntent = PendingIntent.getBroadcast( + context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + + nn.setOnShowIntent(pendingIntent); + notificationNudges.add(nn); } } diff --git a/src/com/android/dialer/discovery/DiscoverySignalReceiver.java b/src/com/android/dialer/discovery/DiscoverySignalReceiver.java index 5ba7ceecc..770609918 100644 --- a/src/com/android/dialer/discovery/DiscoverySignalReceiver.java +++ b/src/com/android/dialer/discovery/DiscoverySignalReceiver.java @@ -1,6 +1,7 @@ package com.android.dialer.discovery; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -12,7 +13,9 @@ import android.util.Log; import com.android.contacts.common.GeoUtil; import com.android.dialer.DialtactsActivity; import com.android.dialer.discovery.DiscoveryService; +import com.android.dialer.incall.InCallMetricsHelper; import com.android.phone.common.incall.CallMethodUtils; +import com.cyanogen.ambient.discovery.util.NudgeKey; import com.google.i18n.phonenumbers.NumberParseException; import com.google.i18n.phonenumbers.PhoneNumberUtil; import com.google.i18n.phonenumbers.Phonemetadata; @@ -23,6 +26,18 @@ public class DiscoverySignalReceiver extends BroadcastReceiver { private static final String TAG = "DiscoverySignalReceiver"; private static final boolean DEBUG_CONNECTIVITY = false; + public static final String DISCOVERY_NUDGE_SHOWN = + "com.android.dialer.discovery.DiscoverySignalReceiver.ON_SHOW"; + + public static final String DISCOVERY_NUDGE_DISMISS = + "com.android.dialer.discovery.DiscoverySignalReceiver.ON_DISMISS"; + + public static final String NUDGE_SHARED_PREF = "nudge_shared_pref"; + + public static final String NUDGE_ID = "nudge_id"; + public static final String NUDGE_KEY = "nudge_key"; + public static final String NUDGE_COMPONENT = "nudge_component"; + @Override public void onReceive(Context context, Intent intent) { @@ -54,6 +69,44 @@ public class DiscoverySignalReceiver extends BroadcastReceiver { startServiceForInternationalCallMade(context); } break; + case DISCOVERY_NUDGE_SHOWN: + String nudgeId = intent.getStringExtra(NUDGE_ID); + String nudgeKey = intent.getStringExtra(NUDGE_KEY); + ComponentName nudgeComponent = + ComponentName.unflattenFromString(intent.getStringExtra(NUDGE_COMPONENT)); + + SharedPreferences sp = context.getSharedPreferences(NUDGE_SHARED_PREF, + Context.MODE_PRIVATE); + + SharedPreferences.Editor editor = sp.edit(); + + NudgeItem ni = new NudgeItem(nudgeComponent, nudgeKey, nudgeId, + System.currentTimeMillis()); + + String countKey = ni.getCountKey(); + String timeKey = ni.getTimeKey(); + + long count = sp.getLong(countKey, 0) + 1; + + editor.putLong(countKey, count); + editor.putLong(timeKey, System.currentTimeMillis()); + editor.apply(); + + InCallMetricsHelper.Events event; + if (nudgeKey.equals(NudgeKey.NOTIFICATION_INTERNATIONAL_CALL)) { + event = InCallMetricsHelper.Events.NUDGE_EVENT_INTL; + } else { + event = InCallMetricsHelper.Events.NUDGE_EVENT_ROAMING; + } + + InCallMetricsHelper.increaseCountOfMetric(nudgeComponent, event, + InCallMetricsHelper.Categories.DISCOVERY_NUDGES, + InCallMetricsHelper.Parameters.COUNT); + + break; + case DISCOVERY_NUDGE_DISMISS: + + break; } } diff --git a/src/com/android/dialer/discovery/NudgeItem.java b/src/com/android/dialer/discovery/NudgeItem.java new file mode 100644 index 000000000..1aacd82a6 --- /dev/null +++ b/src/com/android/dialer/discovery/NudgeItem.java @@ -0,0 +1,93 @@ +package com.android.dialer.discovery; + +import android.content.ComponentName; +import com.android.dialer.incall.InCallMetricsHelper; +import com.google.common.base.Joiner; + +/** + * Helper object for serializing and tracking recorded nudge data. + */ +public class NudgeItem { + + public String mKey; + String mId; + long mShownTime; + public ComponentName mComponent; + + public static final String TIME = "time"; + public static final String NUDGE_ENABLED_PLUGIN = "nudgeEnabledPlugin"; + public static final int RECENT_TIME = 24; + + public static final int COMPONENT = 0; + public static final int KEY = 1; + public static final int ID = 2; + public static final int DATA = 3; + + public NudgeItem(ComponentName componentName, String key, String id, long showntime) { + this.mComponent = componentName; + this.mKey = key; + this.mId = id; + this.mShownTime = showntime; + } + + public static NudgeItem createNudgeItemFromArray(String[] array, Object showTime) { + if (array.length == 4) { + ComponentName cn = ComponentName.unflattenFromString(array[COMPONENT]); + if (showTime instanceof Long) { + return new NudgeItem(cn, array[KEY], array[ID], (Long) showTime); + } + } + return null; + } + + /** + * All of our SharedPrefs keys start with this data. + * @return + */ + private String[] getBaseKey() { + String[] baseKey = new String[4]; + baseKey[COMPONENT] = this.mComponent.flattenToShortString(); + baseKey[KEY] = this.mKey; + baseKey[ID] = this.mId; + return baseKey; + } + + /** + * Get timeKey + */ + public String getTimeKey() { + String[] key = getBaseKey(); + key[DATA] = TIME; + return Joiner.on(InCallMetricsHelper.DELIMIT).join(key); + } + + public String getWinKey() { + String[] key = getBaseKey(); + key[DATA] = NUDGE_ENABLED_PLUGIN; + return Joiner.on(InCallMetricsHelper.DELIMIT).join(key); + } + + /** + * Get countKey + */ + public String getCountKey() { + String[] key = getBaseKey(); + key[DATA] = InCallMetricsHelper.Parameters.COUNT.value(); + return Joiner.on(InCallMetricsHelper.DELIMIT).join(key); + } + + public static String getKeyType(String[] array) { + return array[DATA]; + } + + public boolean belongsTo(ComponentName cn) { + return this.mComponent.equals(cn); + } + + // nudge was shown less than 24 hours ago and now + // the plugin is enabled. Consider this recent. + public boolean isRecent() { + double hoursSince = (this.mShownTime - System.currentTimeMillis()) / 1000 / 60 / 60; + return hoursSince <= RECENT_TIME; + } +} diff --git a/src/com/android/dialer/incall/CallMethodStatusReceiver.java b/src/com/android/dialer/incall/CallMethodStatusReceiver.java index b0d67b1ec..336cdc537 100644 --- a/src/com/android/dialer/incall/CallMethodStatusReceiver.java +++ b/src/com/android/dialer/incall/CallMethodStatusReceiver.java @@ -1,17 +1,24 @@ package com.android.dialer.incall; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import com.android.dialer.DialtactsActivity; +import com.android.dialer.discovery.NudgeItem; import com.android.phone.common.incall.CallMethodHelper; import com.android.phone.common.incall.CallMethodUtils; +import com.android.dialer.incall.InCallMetricsHelper; +import com.android.dialer.discovery.DiscoverySignalReceiver; +import com.cyanogen.ambient.discovery.util.NudgeKey; import com.cyanogen.ambient.plugin.PluginStatus; import com.cyanogen.ambient.plugin.PluginStatusConstants; +import java.util.Map; + public class CallMethodStatusReceiver extends BroadcastReceiver { @Override @@ -26,21 +33,75 @@ public class CallMethodStatusReceiver extends BroadcastReceiver { if (b != null) { String pluginName = b.getString(PluginStatusConstants.EXTRA_PLUGIN_COMPONENT); int pluginStatus = b.getInt(PluginStatusConstants.EXTRA_PLUGIN_STATUS); - if (pluginStatus == PluginStatus.ENABLED) { + int previousStatus = b.getInt(PluginStatusConstants.EXTRA_PLUGIN_PREVIOUS_STATUS); - String lastProviderEnabled = - preferences.getString(CallMethodUtils.PREF_LAST_ENABLED_PROVIDER, null); + InCallMetricsHelper.Events event = null; + switch (pluginStatus) { + case PluginStatus.ENABLED: + event = InCallMetricsHelper.Events.PROVIDER_ENABLED; + String lastProviderEnabled = + preferences.getString(CallMethodUtils.PREF_LAST_ENABLED_PROVIDER, null); - // No provider was previously enabled, show coachmark - if (lastProviderEnabled == null) { - preferences.edit() - .putBoolean(CallMethodUtils.PREF_SPINNER_COACHMARK_SHOW, true).apply(); - } + SharedPreferences.Editor e = preferences.edit(); + + // No provider was previously enabled, show coachmark + if (lastProviderEnabled == null) { + e.putBoolean(CallMethodUtils.PREF_SPINNER_COACHMARK_SHOW, true).apply(); + } + + e.putString(CallMethodUtils.PREF_LAST_ENABLED_PROVIDER, pluginName).apply(); - preferences.edit() - .putString(CallMethodUtils.PREF_LAST_ENABLED_PROVIDER, pluginName).apply(); + if (previousStatus == PluginStatus.HIDDEN) { + checkNudgePluginStatus(context, pluginName); + } + break; + case PluginStatus.HIDDEN: + event = InCallMetricsHelper.Events.PROVIDER_HIDDEN; + break; + case PluginStatus.UNAVAILABLE: + event = InCallMetricsHelper.Events.PROVIDER_UNAVAILABLE; + break; + case PluginStatus.DISABLED: + event = InCallMetricsHelper.Events.PROVIDER_DISABLED; + break; + } + + if (event != null) { + InCallMetricsHelper.increaseCountOfMetric( + ComponentName.unflattenFromString(pluginName), + event, InCallMetricsHelper.Categories.PROVIDER_STATE_CHANGE, + InCallMetricsHelper.Parameters.COUNT_INTERACTIONS); } } } + private void checkNudgePluginStatus(Context context, String pluginName) { + SharedPreferences sp = context.getSharedPreferences( + DiscoverySignalReceiver.NUDGE_SHARED_PREF, Context.MODE_PRIVATE); + + SharedPreferences.Editor editor = sp.edit(); + + Map<String, ?> prefs = sp.getAll(); + + for (Map.Entry<String, ?> entry : prefs.entrySet()) { + String[] keyParts = entry.getKey().split(InCallMetricsHelper.DELIMIT); + ComponentName enabledPlugin = ComponentName.unflattenFromString(pluginName); + NudgeItem ni = NudgeItem.createNudgeItemFromArray(keyParts, entry.getValue()); + if (ni != null) { + if (ni.belongsTo(enabledPlugin)) { + switch(ni.mKey) { + case NudgeKey.NOTIFICATION_INTERNATIONAL_CALL: + case NudgeKey.NOTIFICATION_ROAMING: + if (NudgeItem.getKeyType(keyParts).equals(NudgeItem.TIME)) { + if (ni.isRecent()) { + editor.putBoolean(ni.getWinKey(), true); + editor.apply(); + } + } + break; + } + } + } + } + } } diff --git a/src/com/android/dialer/incall/InCallMetricsHelper.java b/src/com/android/dialer/incall/InCallMetricsHelper.java new file mode 100644 index 000000000..36d5c69b9 --- /dev/null +++ b/src/com/android/dialer/incall/InCallMetricsHelper.java @@ -0,0 +1,322 @@ +package com.android.dialer.incall; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.util.Log; +import com.android.dialer.DialerApplication; +import com.android.dialer.incall.InCallMetricsReceiver; +import com.android.phone.common.ambient.AmbientConnection; +import com.android.phone.common.incall.CallMethodHelper; +import com.android.phone.common.incall.CallMethodInfo; +import com.cyanogen.ambient.analytics.AnalyticsServices; +import com.cyanogen.ambient.analytics.Event; + +import java.util.HashMap; +import java.util.Map; + +public class InCallMetricsHelper { + + private static final String TAG = InCallMetricsHelper.class.getSimpleName(); + private static final boolean DEBUG = false; + + private static final String CATEGORY_BASE = "dialernext.incall."; + private static final String METRICS_SHARED_PREFERENCES = "metrics_shared_preferences"; + public static final String DELIMIT = ":"; + + public enum Categories { + CALLS("CALLS"), + DISCOVERY_NUDGES("DISCOVERY_NUDGES"), + AUTHENTICATION("AUTHENTICATION"), + PROVIDER_STATE_CHANGE("PROVIDER_STATE_CHANGE"), + INAPP_NUDGES("INAPP_NUDGES"); + + private String mValue; + Categories(String s) { + mValue = s; + } + public String value() { + return mValue; + } + } + + public enum Events { + NUDGE_EVENT_INTL("NUDGE_EVENT_INTL"), + NUDGE_EVENT_ROAMING("NUDGE_EVENT_ROAMING"), + CALL_PROVIDER_VIDEO("CALL_PROVIDER_VIDEO"), + CALL_PROVIDER_PSTN("CALL_PROVIDER_PSTN"), + CALL_PROVIDER_VOICE("CALL_PROVIDER_VOICE"), + CALL_SIM_PSTN("CALL_SIM_PSTN"), + AUTH_LOGIN("AUTH_LOGIN"), + AUTH_LOGOUT("AUTH_LOGOUT"), + PROVIDER_HIDDEN("PROVIDER_HIDDEN"), + PROVIDER_ENABLED("PROVIDER_ENABLED"), + PROVIDER_DISABLED("PROVIDER_DISABLED"), + PROVIDER_UNAVAILABLE("PROVIDER_UNAVAILABLE"), + CONTACTS_MERGED("CONTACTS_MERGED"), + INVITES_SENT("INVITES_SENT"), + INAPP_NUDGE_CONTACTS_LOGIN("INAPP_NUDGE_CONTACTS_LOGIN"), + INAPP_NUDGE_DIALER_WIFI("INAPP_NUDGE_DIALER_WIFI"), + INAPP_NUDGE_DIALER_ROAMING("INAPP_NUDGE_DIALER_ROAMING"), + INAPP_NUDGE_CONTACTS_INSTALL("INAPP_NUDGE_CONTACTS_INSTALL"); + + private String mValue; + Events(String s) { + mValue = s; + } + public String value() { + return mValue; + } + } + + public enum Parameters { + EVENT_ACCEPTANCE("EVENT_ACCEPTANCE"), + EVENT_ATTEMPT_NUMBER("EVENT_ATTEMPT_NUMBER"), + NUDGE_ID("NUDGE_ID"), + NUDGE_COUNT("NUDGE_COUNT"), + PROVIDER_NAME("PROVIDER_NAME"), + ACTION_LOCATION("ACTION_LOCATION"), + OUTGOING_SUCCESS("OUTGOING_SUCCESS"), + OUTGOING_FAIL("OUTGOING_FAIL"), + OUTGOING_CONVERSIONS("OUTGOING_CONVERSIONS"), + OUTGOING_TOTAL_DURATION("OUTGOING_TOTAL_DURATION"), + COUNT_AUTOMATIC("COUNT_AUTOMATIC"), + COUNT_MANUAL("COUNT_MANUAL"), + COUNT("COUNT"), + COUNT_INTERACTIONS("COUNT_INTERACTIONS"); + + private String mValue; + Parameters(String s) { + mValue = s; + } + public String value() { + return mValue; + } + } + + private static InCallMetricsHelper sInstance; + private DialerApplication mContext; + + private InCallMetricsHelper() {} + + private static synchronized InCallMetricsHelper getInstance() { + if (sInstance == null) { + sInstance = new InCallMetricsHelper(); + } + return sInstance; + } + + /** + * Start MetricsHelper instance. + * @param context + */ + public static void init(DialerApplication context) { + InCallMetricsHelper helper = getInstance(); + helper.mContext = context; + + AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); + Intent i = new Intent(context, InCallMetricsReceiver.class); + + PendingIntent pi = PendingIntent.getService(context, 0, i, 0); + am.setInexactRepeating(AlarmManager.RTC_WAKEUP, 1000L, // every 24 hours + AlarmManager.INTERVAL_DAY, pi); + } + + /** + * Sends all events + * @param event + */ + private void sendAmbientEvent(Event event) { + if (DEBUG) { + Log.d(TAG, "Event: " + event.toString()); + } + AnalyticsServices.AnalyticsApi.sendEvent(AmbientConnection.CLIENT.get(mContext), event); + } + + /** + * Prepares the event to send + * @param category of the event + * @param action that the event is about + * @param params fields that are part of the event + * @param cn associated with the plugin + */ + public static void sendEvent(Categories category, Events action, + HashMap<Parameters, Object> params, ComponentName cn) { + Event.Builder event = new Event.Builder(CATEGORY_BASE + category.value(), action.value()); + if (params != null && params.size() > 0) { + for (Parameters p : params.keySet()) { + event.addField(p.value(), String.valueOf(params.get(p))); + } + } + Event e = event.build(); + CallMethodHelper.shipAnalyticsToPlugin(cn, e); + getInstance().sendAmbientEvent(e); + } + + /** + * Stores events in SharedPreferences until we need to send them out. + * @param cn + * @param category + * @param event + * @param data + */ + public static void storeEvent(ComponentName cn, Categories category, Events event, + HashMap<Parameters, Object> data) { + storeEvent(cn, category, event, data, null); + } + + public static void storeEvent(ComponentName cn, Categories category, Events event, + HashMap<Parameters, Object> data, String location) { + + SharedPreferences sp = getInstance().mContext.getSharedPreferences( + METRICS_SHARED_PREFERENCES, Context.MODE_PRIVATE); + + SharedPreferences.Editor editor = sp.edit(); + + for (Parameters param : data.keySet()) { + Object o = data.get(param); + String eventKey = cn.toShortString() + DELIMIT + category.value() + DELIMIT + + event.value() + DELIMIT + param.value(); + if (location != null) { + eventKey += DELIMIT + location; + } + putEditor(editor, eventKey, o); + } + editor.apply(); + } + + /** + * Increases the count of a parameter by one + * @param hashmap containing the parameter + * @param p parameter + * @return new count of the item. + */ + public static int increaseCount(HashMap<Parameters, Object> hashmap, Parameters p) { + if (hashmap.containsKey(p)) { + return (int)hashmap.get(p) + 1; + } else { + return 1; + } + } + + /** + * Helper method for putting objects into a shared preference + * @param e + * @param key + * @param value + */ + private static void putEditor(SharedPreferences.Editor e, String key, Object value) { + if (value instanceof Integer) { + e.putInt(key, (int)value); + } else if (value instanceof String) { + e.putString(key, String.valueOf(value)); + } else if (value instanceof Boolean) { + e.putBoolean(key, (boolean)value); + } else if (value instanceof Double) { + e.putLong(key, ((Double) value).longValue()); + } else if (value instanceof Long) { + e.putLong(key, (long)value); + } + } + + /** + * Get the sharedpreferences events and output a hashmap for the event's values. + * @param cn + * @param category + * @param event + * @return + */ + public static HashMap<Parameters, Object> getStoredEventParams(ComponentName cn, + Categories category, + Events event) { + + SharedPreferences sp = getInstance().mContext.getSharedPreferences( + METRICS_SHARED_PREFERENCES, Context.MODE_PRIVATE); + + String eventKey = cn.toShortString() + DELIMIT + category.value() + DELIMIT + event.value(); + + HashMap<Parameters, Object> eventMap = new HashMap<>(); + Map<String, ?> map = sp.getAll(); + + for(Map.Entry<String,?> entry : map.entrySet()) { + if (entry.getKey().startsWith(eventKey)) { + String[] keyParts = entry.getKey().split(DELIMIT); + String key; + if (keyParts.length > 4) { + key = keyParts[keyParts.length - 2]; + } else { + key = keyParts[keyParts.length - 1]; + } + eventMap.put(Parameters.valueOf(key), entry.getValue()); + } + } + return eventMap; + } + + /** + * Helper method to increase the count of a specific parameter + * @param cn + * @param event + * @param cat + * @param param + */ + public static void increaseCountOfMetric(ComponentName cn, Events event, Categories cat, + Parameters param) { + HashMap<Parameters, Object> metricsData = getStoredEventParams(cn, cat, event); + metricsData.put(param, increaseCount(metricsData,param)); + storeEvent(cn, cat, event, metricsData); + } + + public static void increaseCountOfMetric(CallMethodInfo cmi, Events event, Categories cat, + Parameters param) { + increaseCountOfMetric(cmi.mComponent, event, cat, param); + } + + + + /** + * Prepares all our metrics for sending. + */ + public static void prepareToSend() { + SharedPreferences sp = getInstance().mContext.getSharedPreferences( + METRICS_SHARED_PREFERENCES, Context.MODE_PRIVATE); + + Map<String, ?> map = sp.getAll(); + + HashMap<String, HashMap<Parameters, Object>> items = new HashMap<>(); + for(Map.Entry<String,?> entry : map.entrySet()){ + + String[] keyParts = entry.getKey().split(DELIMIT); + + ComponentName component = ComponentName.unflattenFromString(keyParts[0]); + Categories category = Categories.valueOf(keyParts[1]); + Events event = Events.valueOf(keyParts[2]); + Parameters parm = Parameters.valueOf(keyParts[3]); + + String eventKey = component.toShortString() + DELIMIT + category.value() + DELIMIT + + event.value(); + + if (!items.containsKey(eventKey)) { + items.put(eventKey, new HashMap<Parameters, Object>()); + } + + HashMap<Parameters, Object> params = items.get(eventKey); + params.put(parm, entry.getValue()); + items.put(eventKey, params); + } + + for (String key : items.keySet()) { + String[] keyParts = key.split(DELIMIT); + ComponentName component = ComponentName.unflattenFromString(keyParts[0]); + Categories category = Categories.valueOf(keyParts[1]); + Events event = Events.valueOf(keyParts[2]); + HashMap<Parameters, Object> params = items.get(key); + sendEvent(category, event, params, component); + } + } + + +} diff --git a/src/com/android/dialer/incall/InCallMetricsReceiver.java b/src/com/android/dialer/incall/InCallMetricsReceiver.java new file mode 100644 index 000000000..4706678f8 --- /dev/null +++ b/src/com/android/dialer/incall/InCallMetricsReceiver.java @@ -0,0 +1,200 @@ +package com.android.dialer.incall; + +import android.app.IntentService; +import android.content.ComponentName; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.provider.CallLog; +import android.telephony.PhoneNumberUtils; +import android.util.Log; +import com.cyanogen.ambient.incall.CallLogConstants; +import com.android.dialer.incall.InCallMetricsHelper; +import cyanogenmod.providers.CMSettings; + +import java.util.HashMap; + +/** + * InCallMetricsReceiver is an aggregation and shipping service that is fired + * once every 24 hours to pass Metrics to ModCore's analytics service. + * + * InCallMetrics is responsible for retrieving call data from the call log as well + * as events logged to shared preferences. + * + * InCall Metrics is used to send other data besides just InCallPlugin Metrics, + * as we currently are aggregating sim calls for better UI/UX data; + */ +public class InCallMetricsReceiver extends IntentService { + + public InCallMetricsReceiver() { + super(InCallMetricsReceiver.class.getSimpleName()); + } + + private static final String TAG = InCallMetricsHelper.class.getSimpleName(); + private static final boolean DEBUG = false; + + private static final String CALL_COUNT_SUCCESS = "call_count_success"; + private static final String CALL_COUNT_FAILURE = "call_count_failure"; + private static final String CALL_IS_PSTN = "call_is_pstn"; + private static final String ORIGIN = "origin"; + + @Override + protected void onHandleIntent(Intent intent) { + lookupCallsSince(0); + + // Send stored Incall Specific events + if (CMSettings.Secure.getInt(getApplicationContext().getContentResolver(), + CMSettings.Secure.STATS_COLLECTION, 1) != 0) { + InCallMetricsHelper.prepareToSend(); + } + } + + public InCallMetricsReceiver(String name) { + super(name); + } + + private void lookupCallsSince(long time) { + Uri uri = CallLogConstants.CONTENT_ALL_URI.buildUpon().build(); + + String[] projection = new String[] { + CallLog.Calls.DURATION, + CallLog.Calls.DATE, + CallLogConstants.PLUGIN_PACKAGE_NAME, + CallLog.Calls.NUMBER, + ORIGIN + }; + + String where = CallLog.Calls.DATE + " > ?"; + + String[] args = new String[] { + String.valueOf(time) + }; + + Cursor c = getContentResolver().query(uri, projection, where, args, null); + + HashMap<String, HashMap<String, Object>> keys = new HashMap<>(); + + while (c.moveToNext()) { + String pluginComponent = + c.getString(c.getColumnIndex(CallLogConstants.PLUGIN_PACKAGE_NAME)); + + String callOrigin = + c.getString(c.getColumnIndex(ORIGIN)); + + long callDuration = + c.getLong(c.getColumnIndex(CallLog.Calls.DURATION)); + + boolean isPSTN = + PhoneNumberUtils.isGlobalPhoneNumber( + c.getString( + c.getColumnIndex(CallLog.Calls.NUMBER))); + + if (callOrigin == null) { + // mark this as an unknown location call; + callOrigin = "unknown"; + } + + if (pluginComponent == null) { + // TODO: figure out which sim the call was placed through + pluginComponent = "sim"; + } + pluginComponent += InCallMetricsHelper.DELIMIT + callOrigin; + HashMap<String, Object> data; + long call = 1; + long calls_success = 0; + long calls_failure = 0; + if (keys.containsKey(pluginComponent)) { + data = keys.get(pluginComponent); + if (data.containsKey(CallLog.Calls.DURATION)) { + // Determine if the call was successfully connected to another device + // for a period of time. (includes: people, voicemails, machines) + // TODO: figure out how to fix false positive for calls that immediately end + // once they are started + if (callDuration > 0) { + // Call succeeded, increase by one + calls_success += call; + if (data.containsKey(CALL_COUNT_SUCCESS)) { + // add previous success to our current count; + calls_success += (long) data.get(CALL_COUNT_SUCCESS); + } + if (data.containsKey(CALL_COUNT_FAILURE)) { + // call success, keep old failure count w/o modification + calls_failure = (long) data.get(CALL_COUNT_FAILURE); + } + } else { + // Call failed to connect, increase by one + calls_failure += call; + if (data.containsKey(CALL_COUNT_SUCCESS)) { + // Not a successful call, keep old count + calls_success = (long) data.get(CALL_COUNT_SUCCESS); + } + if (data.containsKey(CALL_COUNT_FAILURE)) { + // add previous failures to our current count of one + calls_failure += (long) data.get(CALL_COUNT_FAILURE); + } + } + callDuration += (long) data.get(CallLog.Calls.DURATION); + } + } else { + data = new HashMap<>(); + if (callDuration > 0) { + calls_success = call; + } else { + calls_failure = call; + } + } + data.put(CallLog.Calls.DURATION, callDuration); + data.put(CALL_COUNT_SUCCESS, calls_success); + data.put(CALL_COUNT_FAILURE, calls_failure); + data.put(CALL_IS_PSTN, isPSTN); + keys.put(pluginComponent, data); + } + + c.close(); + + for (String key : keys.keySet()) { + // Shippit + HashMap<String, Object> value = keys.get(key); + String[] keySplit = key.split(InCallMetricsHelper.DELIMIT); + String pluginComponent = keySplit[0]; + String callOrigin = keySplit[1]; + long callDuration = (Long)value.get(CallLog.Calls.DURATION); + long callCountSuccess = (Long)value.get(CALL_COUNT_SUCCESS); + long callCountFailure = (Long)value.get(CALL_COUNT_FAILURE); + boolean isPSTN = (Boolean) value.get(CALL_IS_PSTN); + + if (DEBUG) { + Log.v(TAG, "Method:" + pluginComponent + + " Origin:" + callOrigin + + " CountSuccess:" + callCountSuccess + + " CountFailure:" + callCountFailure + + " Duration:" + callDuration); + } + + HashMap<InCallMetricsHelper.Parameters, Object> params = new HashMap<>(); + params.put(InCallMetricsHelper.Parameters.OUTGOING_SUCCESS, callCountSuccess); + params.put(InCallMetricsHelper.Parameters.OUTGOING_FAIL, callCountFailure); + params.put(InCallMetricsHelper.Parameters.OUTGOING_TOTAL_DURATION, callDuration); + params.put(InCallMetricsHelper.Parameters.PROVIDER_NAME, pluginComponent); + params.put(InCallMetricsHelper.Parameters.ACTION_LOCATION, callOrigin); + + InCallMetricsHelper.Events event; + if (pluginComponent.startsWith("sim")) { + event = InCallMetricsHelper.Events.CALL_SIM_PSTN; + } else { + if (isPSTN) { + event = InCallMetricsHelper.Events.CALL_PROVIDER_VOICE; + } else { + event = InCallMetricsHelper.Events.CALL_PROVIDER_PSTN; + } + } + + InCallMetricsHelper.sendEvent(InCallMetricsHelper.Categories.CALLS, event, params, + ComponentName.unflattenFromString(pluginComponent)); + + } + + } + +} + diff --git a/src/com/android/dialer/interactions/PhoneNumberInteraction.java b/src/com/android/dialer/interactions/PhoneNumberInteraction.java index d0dae3b1a..f24371d44 100644 --- a/src/com/android/dialer/interactions/PhoneNumberInteraction.java +++ b/src/com/android/dialer/interactions/PhoneNumberInteraction.java @@ -59,6 +59,7 @@ import com.android.dialer.util.IntentUtil; import com.android.dialer.util.DialerUtils; import com.android.phone.common.incall.CallMethodHelper; +import com.cyanogen.ambient.incall.extension.OriginCodes; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; @@ -355,7 +356,10 @@ public class PhoneNumberInteraction implements OnLoadCompleteListener<Cursor> { } break; } - DialerUtils.startActivityWithErrorToast(context, intent); + if (callOrigin == null) { + callOrigin = OriginCodes.CONTACTS_TAB; + } + DialerUtils.startActivityWithErrorToast(context, intent, callOrigin); } /** diff --git a/src/com/android/dialer/util/DialerUtils.java b/src/com/android/dialer/util/DialerUtils.java index e25ada59d..593d35c26 100644 --- a/src/com/android/dialer/util/DialerUtils.java +++ b/src/com/android/dialer/util/DialerUtils.java @@ -44,6 +44,8 @@ import com.android.dialer.widget.EmptyContentView; import com.android.incallui.CallCardFragment; import com.android.incallui.Log; +import com.android.phone.common.PhoneConstants; + import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -62,7 +64,19 @@ public class DialerUtils { * @param intent to start the activity with. */ public static void startActivityWithErrorToast(Context context, Intent intent) { - startActivityWithErrorToast(context, intent, R.string.activity_not_available); + startActivityWithErrorToast(context, intent, R.string.activity_not_available, null); + } + + /** + * Attempts to start an activity and displays a toast with the default error message if the + * activity is not found, instead of throwing an exception. + * + * @param context to start the activity with. + * @param intent to start the activity with. + * @param origin that started the call + */ + public static void startActivityWithErrorToast(Context context, Intent intent, String origin) { + startActivityWithErrorToast(context, intent, R.string.activity_not_available, origin); } /** @@ -75,6 +89,21 @@ public class DialerUtils { * not found. */ public static void startActivityWithErrorToast(Context context, Intent intent, int msgId) { + startActivityWithErrorToast(context, intent, msgId, null); + } + + /** + * Attempts to start an activity and displays a toast with a provided error message if the + * activity is not found, instead of throwing an exception. + * + * @param context to start the activity with. + * @param intent to start the activity with. + * @param msgId Resource ID of the string to display in an error message if the activity is + * not found. + * @param origin Area of the dialer app where the activity was started. + */ + public static void startActivityWithErrorToast(Context context, Intent intent, int msgId, + String origin) { try { if ((IntentUtil.CALL_ACTION.equals(intent.getAction()) && context instanceof Activity)) { @@ -85,6 +114,9 @@ public class DialerUtils { extras.putParcelable(TouchPointManager.TOUCH_POINT, touchPoint); intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras); } + if (origin != null) { + intent.putExtra(PhoneConstants.EXTRA_CALL_ORIGIN, origin); + } final TelecomManager tm = (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE); tm.placeCall(intent.getData(), intent.getExtras()); diff --git a/src/com/android/dialer/util/IntentUtil.java b/src/com/android/dialer/util/IntentUtil.java index 2ce3bd1f8..e1a495066 100644 --- a/src/com/android/dialer/util/IntentUtil.java +++ b/src/com/android/dialer/util/IntentUtil.java @@ -26,6 +26,7 @@ import android.telecom.VideoProfile; import com.android.contacts.common.CallUtil; import com.android.phone.common.PhoneConstants; +import com.cyanogen.ambient.incall.extension.OriginCodes; /** * Utilities for creation of intents in Dialer, such as {@link Intent#ACTION_CALL}. @@ -41,37 +42,38 @@ public class IntentUtil { * automatically. */ public static Intent getCallIntent(String number) { - return getCallIntent(number, null, null); + return getCallIntent(number, null); } /** - * Return an Intent for making a phone call. A given Uri will be used as is (without any - * sanity check). + * Return an Intent for making a phone call. Scheme (e.g. tel, sip) will be determined + * automatically. */ - public static Intent getCallIntent(Uri uri) { - return getCallIntent(uri, null, null); + public static Intent getCallIntent(String number, String origin) { + return getCallIntent(number, origin, null); } /** - * A variant of {@link #getCallIntent(String)} but also accept a call origin. - * For more information about call origin, see comments in Phone package (PhoneApp). + * Return an Intent for making a phone call. A given Uri will be used as is (without any + * sanity check). */ - public static Intent getCallIntent(String number, String callOrigin) { - return getCallIntent(CallUtil.getCallUri(number), callOrigin, null); + public static Intent getCallIntent(Uri uri, String origin) { + return getCallIntent(uri, origin, null); } /** * A variant of {@link #getCallIntent(String)} but also include {@code Account}. */ - public static Intent getCallIntent(String number, PhoneAccountHandle accountHandle) { - return getCallIntent(number, null, accountHandle); + public static Intent getCallIntent(String number, PhoneAccountHandle accountHandle, + String origin) { + return getCallIntent(number, origin, accountHandle); } /** * A variant of {@link #getCallIntent(android.net.Uri)} but also include {@code Account}. */ - public static Intent getCallIntent(Uri uri, PhoneAccountHandle accountHandle) { - return getCallIntent(uri, null, accountHandle); + public static Intent getCallIntent(Uri uri, PhoneAccountHandle accountHandle, String origin) { + return getCallIntent(uri, origin, accountHandle); } /** @@ -115,15 +117,17 @@ public class IntentUtil { * A variant of {@link #getCallIntent(String, String, android.telecom.PhoneAccountHandle)} for * starting a video call. */ - public static Intent getVideoCallIntent(String number, PhoneAccountHandle accountHandle) { - return getVideoCallIntent(number, null, accountHandle); + public static Intent getVideoCallIntent(String number, PhoneAccountHandle accountHandle, + String origin) { + return getVideoCallIntent(number, origin, accountHandle); } /** * A variant of {@link #getCallIntent(android.net.Uri)} for calling Voicemail. */ public static Intent getVoicemailIntent() { - return getCallIntent(Uri.fromParts(PhoneAccount.SCHEME_VOICEMAIL, "", null)); + return getCallIntent(Uri.fromParts(PhoneAccount.SCHEME_VOICEMAIL, "", null), + OriginCodes.GENERIC_CALL); } /** |
