1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.dialer.callintent; 18 19 import android.content.Intent; 20 import android.net.Uri; 21 import android.os.Bundle; 22 import android.os.Parcel; 23 import android.os.Parcelable; 24 import android.os.SystemClock; 25 import android.support.annotation.NonNull; 26 import android.support.annotation.Nullable; 27 import android.support.annotation.VisibleForTesting; 28 import android.telecom.PhoneAccount; 29 import android.telecom.PhoneAccountHandle; 30 import android.telecom.TelecomManager; 31 import android.telecom.VideoProfile; 32 import android.text.TextUtils; 33 import com.android.dialer.common.Assert; 34 import com.android.dialer.performancereport.PerformanceReport; 35 import com.android.dialer.util.CallUtil; 36 import com.google.auto.value.AutoValue; 37 import com.google.common.collect.ImmutableMap; 38 import com.google.protobuf.InvalidProtocolBufferException; 39 40 /** Creates an intent to start a new outgoing call. */ 41 @AutoValue 42 public abstract class CallIntent implements Parcelable { 43 private static int lightbringerButtonAppearInExpandedCallLogItemCount = 0; 44 private static int lightbringerButtonAppearInCollapsedCallLogItemCount = 0; 45 private static int lightbringerButtonAppearInSearchCount = 0; 46 number()47 abstract Uri number(); 48 callSpecificAppData()49 abstract CallSpecificAppData callSpecificAppData(); 50 51 @Nullable phoneAccountHandle()52 abstract PhoneAccountHandle phoneAccountHandle(); 53 isVideoCall()54 abstract boolean isVideoCall(); 55 56 @Nullable callSubject()57 abstract String callSubject(); 58 allowAssistedDial()59 abstract boolean allowAssistedDial(); 60 stringInCallUiIntentExtras()61 abstract ImmutableMap<String, String> stringInCallUiIntentExtras(); 62 longInCallUiIntentExtras()63 abstract ImmutableMap<String, Long> longInCallUiIntentExtras(); 64 stringPlaceCallExtras()65 abstract ImmutableMap<String, String> stringPlaceCallExtras(); 66 longPlaceCallExtras()67 abstract ImmutableMap<String, Long> longPlaceCallExtras(); 68 builder()69 public static Builder builder() { 70 return new AutoValue_CallIntent.Builder().setIsVideoCall(false).setAllowAssistedDial(false); 71 } 72 toBuilder()73 public abstract Builder toBuilder(); 74 75 /** Builder class for CallIntent info. */ 76 @AutoValue.Builder 77 public abstract static class Builder { setTelNumber(String number)78 public Builder setTelNumber(String number) { 79 return setNumber(CallUtil.getCallUri(Assert.isNotNull(number))); 80 } 81 setVoicemailNumber(@ullable PhoneAccountHandle phoneAccountHandle)82 public Builder setVoicemailNumber(@Nullable PhoneAccountHandle phoneAccountHandle) { 83 return setNumber(Uri.fromParts(PhoneAccount.SCHEME_VOICEMAIL, "", null)) 84 .setPhoneAccountHandle(phoneAccountHandle); 85 } 86 setNumber(@onNull Uri number)87 public abstract Builder setNumber(@NonNull Uri number); 88 setCallInitiationType(CallInitiationType.Type callInitiationType)89 public Builder setCallInitiationType(CallInitiationType.Type callInitiationType) { 90 return setCallSpecificAppData( 91 CallSpecificAppData.newBuilder().setCallInitiationType(callInitiationType).build()); 92 } 93 callSpecificAppData()94 abstract CallSpecificAppData callSpecificAppData(); 95 setCallSpecificAppData( @onNull CallSpecificAppData callSpecificAppData)96 public abstract Builder setCallSpecificAppData( 97 @NonNull CallSpecificAppData callSpecificAppData); 98 setPhoneAccountHandle(PhoneAccountHandle phoneAccountHandle)99 public abstract Builder setPhoneAccountHandle(PhoneAccountHandle phoneAccountHandle); 100 setIsVideoCall(boolean isVideoCall)101 public abstract Builder setIsVideoCall(boolean isVideoCall); 102 setCallSubject(String callSubject)103 public abstract Builder setCallSubject(String callSubject); 104 setAllowAssistedDial(boolean allowAssistedDial)105 public abstract Builder setAllowAssistedDial(boolean allowAssistedDial); 106 stringInCallUiIntentExtrasBuilder()107 abstract ImmutableMap.Builder<String, String> stringInCallUiIntentExtrasBuilder(); 108 longInCallUiIntentExtrasBuilder()109 abstract ImmutableMap.Builder<String, Long> longInCallUiIntentExtrasBuilder(); 110 addInCallUiIntentExtra(String key, String value)111 public Builder addInCallUiIntentExtra(String key, String value) { 112 stringInCallUiIntentExtrasBuilder().put(key, value); 113 return this; 114 } 115 addInCallUiIntentExtra(String key, Long value)116 public Builder addInCallUiIntentExtra(String key, Long value) { 117 longInCallUiIntentExtrasBuilder().put(key, value); 118 return this; 119 } 120 stringPlaceCallExtrasBuilder()121 abstract ImmutableMap.Builder<String, String> stringPlaceCallExtrasBuilder(); 122 longPlaceCallExtrasBuilder()123 abstract ImmutableMap.Builder<String, Long> longPlaceCallExtrasBuilder(); 124 addPlaceCallExtra(String key, String value)125 public Builder addPlaceCallExtra(String key, String value) { 126 stringPlaceCallExtrasBuilder().put(key, value); 127 return this; 128 } 129 addPlaceCallExtra(String key, Long value)130 public Builder addPlaceCallExtra(String key, Long value) { 131 longPlaceCallExtrasBuilder().put(key, value); 132 return this; 133 } 134 autoBuild()135 abstract CallIntent autoBuild(); 136 build()137 public Intent build() { 138 CallSpecificAppData.Builder builder = 139 CallSpecificAppData.newBuilder(callSpecificAppData()) 140 .setLightbringerButtonAppearInExpandedCallLogItemCount( 141 lightbringerButtonAppearInExpandedCallLogItemCount) 142 .setLightbringerButtonAppearInCollapsedCallLogItemCount( 143 lightbringerButtonAppearInCollapsedCallLogItemCount) 144 .setLightbringerButtonAppearInSearchCount(lightbringerButtonAppearInSearchCount); 145 lightbringerButtonAppearInExpandedCallLogItemCount = 0; 146 lightbringerButtonAppearInCollapsedCallLogItemCount = 0; 147 lightbringerButtonAppearInSearchCount = 0; 148 149 if (PerformanceReport.isRecording()) { 150 builder 151 .setTimeSinceAppLaunch(PerformanceReport.getTimeSinceAppLaunch()) 152 .setTimeSinceFirstClick(PerformanceReport.getTimeSinceFirstClick()) 153 .addAllUiActionsSinceAppLaunch(PerformanceReport.getActions()) 154 .addAllUiActionTimestampsSinceAppLaunch(PerformanceReport.getActionTimestamps()) 155 .setStartingTabIndex(PerformanceReport.getStartingTabIndex()) 156 .build(); 157 PerformanceReport.stopRecording(); 158 } 159 160 setCallSpecificAppData(builder.build()); 161 162 // Validate CallIntent. 163 CallIntent callIntent = autoBuild(); 164 Assert.isNotNull(callIntent.number()); 165 Assert.isNotNull(callIntent.callSpecificAppData()); 166 Assert.checkArgument( 167 callIntent.callSpecificAppData().getCallInitiationType() 168 != CallInitiationType.Type.UNKNOWN_INITIATION); 169 170 return autoBuild().newIntent(); 171 } 172 } 173 174 // Creates the intent which can start a call newIntent()175 private Intent newIntent() { 176 Intent intent = new Intent(Intent.ACTION_CALL, number()); 177 178 intent.putExtra( 179 TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, 180 isVideoCall() ? VideoProfile.STATE_BIDIRECTIONAL : VideoProfile.STATE_AUDIO_ONLY); 181 182 Bundle inCallUiIntentExtras = createInCallUiIntentExtras(); 183 inCallUiIntentExtras.putLong( 184 Constants.EXTRA_CALL_CREATED_TIME_MILLIS, SystemClock.elapsedRealtime()); 185 186 intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, inCallUiIntentExtras); 187 188 if (phoneAccountHandle() != null) { 189 intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle()); 190 } 191 192 if (!TextUtils.isEmpty(callSubject())) { 193 intent.putExtra(TelecomManager.EXTRA_CALL_SUBJECT, callSubject()); 194 } 195 196 intent.putExtras(createPlaceCallExtras()); 197 198 return intent; 199 } 200 createInCallUiIntentExtras()201 private Bundle createInCallUiIntentExtras() { 202 Bundle bundle = new Bundle(); 203 stringInCallUiIntentExtras().forEach(bundle::putString); 204 longInCallUiIntentExtras().forEach(bundle::putLong); 205 CallIntentParser.putCallSpecificAppData(bundle, callSpecificAppData()); 206 return bundle; 207 } 208 createPlaceCallExtras()209 private Bundle createPlaceCallExtras() { 210 Bundle bundle = new Bundle(); 211 stringPlaceCallExtras().forEach(bundle::putString); 212 longPlaceCallExtras().forEach(bundle::putLong); 213 CallIntentParser.putCallSpecificAppData(bundle, callSpecificAppData()); 214 return bundle; 215 } 216 increaseLightbringerCallButtonAppearInExpandedCallLogItemCount()217 public static void increaseLightbringerCallButtonAppearInExpandedCallLogItemCount() { 218 CallIntent.lightbringerButtonAppearInExpandedCallLogItemCount++; 219 } 220 increaseLightbringerCallButtonAppearInCollapsedCallLogItemCount()221 public static void increaseLightbringerCallButtonAppearInCollapsedCallLogItemCount() { 222 CallIntent.lightbringerButtonAppearInCollapsedCallLogItemCount++; 223 } 224 increaseLightbringerCallButtonAppearInSearchCount()225 public static void increaseLightbringerCallButtonAppearInSearchCount() { 226 CallIntent.lightbringerButtonAppearInSearchCount++; 227 } 228 229 @VisibleForTesting getLightbringerButtonAppearInExpandedCallLogItemCount()230 public static int getLightbringerButtonAppearInExpandedCallLogItemCount() { 231 return lightbringerButtonAppearInExpandedCallLogItemCount; 232 } 233 234 @VisibleForTesting getLightbringerButtonAppearInCollapsedCallLogItemCount()235 public static int getLightbringerButtonAppearInCollapsedCallLogItemCount() { 236 return lightbringerButtonAppearInCollapsedCallLogItemCount; 237 } 238 239 @VisibleForTesting getLightbringerButtonAppearInSearchCount()240 public static int getLightbringerButtonAppearInSearchCount() { 241 return lightbringerButtonAppearInSearchCount; 242 } 243 244 @VisibleForTesting(otherwise = VisibleForTesting.NONE) clearLightbringerCounts()245 public static void clearLightbringerCounts() { 246 lightbringerButtonAppearInCollapsedCallLogItemCount = 0; 247 lightbringerButtonAppearInExpandedCallLogItemCount = 0; 248 lightbringerButtonAppearInSearchCount = 0; 249 } 250 251 @Override describeContents()252 public int describeContents() { 253 return 0; 254 } 255 256 @Override writeToParcel(Parcel dest, int flags)257 public void writeToParcel(Parcel dest, int flags) { 258 dest.writeParcelable(number(), flags); 259 dest.writeByteArray(callSpecificAppData().toByteArray()); 260 dest.writeParcelable(phoneAccountHandle(), flags); 261 dest.writeInt(isVideoCall() ? 1 : 0); 262 dest.writeString(callSubject()); 263 dest.writeInt(allowAssistedDial() ? 1 : 0); 264 Bundle stringInCallUiIntentExtrasBundle = new Bundle(); 265 stringInCallUiIntentExtras().forEach(stringInCallUiIntentExtrasBundle::putString); 266 dest.writeBundle(stringInCallUiIntentExtrasBundle); 267 Bundle longInCallUiIntentExtrasBundle = new Bundle(); 268 longInCallUiIntentExtras().forEach(longInCallUiIntentExtrasBundle::putLong); 269 dest.writeBundle(longInCallUiIntentExtrasBundle); 270 } 271 272 // @TODO(justinmcclain): Investigate deleting the parcelable logic and instead switching 273 // to using an internal proto for serialization. 274 public static final Creator<CallIntent> CREATOR = 275 new Creator<CallIntent>() { 276 @Override 277 public CallIntent createFromParcel(Parcel source) { 278 CallIntent.Builder callIntentBuilder = builder(); 279 ClassLoader classLoader = CallIntent.class.getClassLoader(); 280 callIntentBuilder.setNumber(source.readParcelable(classLoader)); 281 CallSpecificAppData data; 282 try { 283 data = CallSpecificAppData.parseFrom(source.createByteArray()); 284 } catch (InvalidProtocolBufferException e) { 285 data = CallSpecificAppData.getDefaultInstance(); 286 } 287 callIntentBuilder 288 .setCallSpecificAppData(data) 289 .setPhoneAccountHandle(source.readParcelable(classLoader)) 290 .setIsVideoCall(source.readInt() != 0) 291 .setCallSubject(source.readString()) 292 .setAllowAssistedDial(source.readInt() != 0); 293 Bundle stringInCallUiIntentExtrasBundle = source.readBundle(classLoader); 294 for (String key : stringInCallUiIntentExtrasBundle.keySet()) { 295 callIntentBuilder.addInCallUiIntentExtra( 296 key, stringInCallUiIntentExtrasBundle.getString(key)); 297 } 298 Bundle longInCallUiIntentExtrasBundle = source.readBundle(classLoader); 299 for (String key : longInCallUiIntentExtrasBundle.keySet()) { 300 callIntentBuilder.addInCallUiIntentExtra( 301 key, longInCallUiIntentExtrasBundle.getLong(key)); 302 } 303 return callIntentBuilder.autoBuild(); 304 } 305 306 @Override 307 public CallIntent[] newArray(int size) { 308 return new CallIntent[0]; 309 } 310 }; 311 } 312