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 PerformanceReport.stopRecording(); 157 } 158 159 setCallSpecificAppData(builder.build()); 160 161 // Validate CallIntent. 162 CallIntent callIntent = autoBuild(); 163 Assert.isNotNull(callIntent.number()); 164 Assert.isNotNull(callIntent.callSpecificAppData()); 165 Assert.checkArgument( 166 callIntent.callSpecificAppData().getCallInitiationType() 167 != CallInitiationType.Type.UNKNOWN_INITIATION); 168 169 return autoBuild().newIntent(); 170 } 171 } 172 173 // Creates the intent which can start a call newIntent()174 private Intent newIntent() { 175 Intent intent = new Intent(Intent.ACTION_CALL, number()); 176 177 intent.putExtra( 178 TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, 179 isVideoCall() ? VideoProfile.STATE_BIDIRECTIONAL : VideoProfile.STATE_AUDIO_ONLY); 180 181 Bundle inCallUiIntentExtras = createInCallUiIntentExtras(); 182 inCallUiIntentExtras.putLong( 183 Constants.EXTRA_CALL_CREATED_TIME_MILLIS, SystemClock.elapsedRealtime()); 184 185 intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, inCallUiIntentExtras); 186 187 if (phoneAccountHandle() != null) { 188 intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle()); 189 } 190 191 if (!TextUtils.isEmpty(callSubject())) { 192 intent.putExtra(TelecomManager.EXTRA_CALL_SUBJECT, callSubject()); 193 } 194 195 intent.putExtras(createPlaceCallExtras()); 196 197 return intent; 198 } 199 createInCallUiIntentExtras()200 private Bundle createInCallUiIntentExtras() { 201 Bundle bundle = new Bundle(); 202 stringInCallUiIntentExtras().forEach(bundle::putString); 203 longInCallUiIntentExtras().forEach(bundle::putLong); 204 CallIntentParser.putCallSpecificAppData(bundle, callSpecificAppData()); 205 return bundle; 206 } 207 createPlaceCallExtras()208 private Bundle createPlaceCallExtras() { 209 Bundle bundle = new Bundle(); 210 stringPlaceCallExtras().forEach(bundle::putString); 211 longPlaceCallExtras().forEach(bundle::putLong); 212 CallIntentParser.putCallSpecificAppData(bundle, callSpecificAppData()); 213 return bundle; 214 } 215 increaseLightbringerCallButtonAppearInExpandedCallLogItemCount()216 public static void increaseLightbringerCallButtonAppearInExpandedCallLogItemCount() { 217 CallIntent.lightbringerButtonAppearInExpandedCallLogItemCount++; 218 } 219 increaseLightbringerCallButtonAppearInCollapsedCallLogItemCount()220 public static void increaseLightbringerCallButtonAppearInCollapsedCallLogItemCount() { 221 CallIntent.lightbringerButtonAppearInCollapsedCallLogItemCount++; 222 } 223 increaseLightbringerCallButtonAppearInSearchCount()224 public static void increaseLightbringerCallButtonAppearInSearchCount() { 225 CallIntent.lightbringerButtonAppearInSearchCount++; 226 } 227 228 @VisibleForTesting getLightbringerButtonAppearInExpandedCallLogItemCount()229 public static int getLightbringerButtonAppearInExpandedCallLogItemCount() { 230 return lightbringerButtonAppearInExpandedCallLogItemCount; 231 } 232 233 @VisibleForTesting getLightbringerButtonAppearInCollapsedCallLogItemCount()234 public static int getLightbringerButtonAppearInCollapsedCallLogItemCount() { 235 return lightbringerButtonAppearInCollapsedCallLogItemCount; 236 } 237 238 @VisibleForTesting getLightbringerButtonAppearInSearchCount()239 public static int getLightbringerButtonAppearInSearchCount() { 240 return lightbringerButtonAppearInSearchCount; 241 } 242 243 @VisibleForTesting(otherwise = VisibleForTesting.NONE) clearLightbringerCounts()244 public static void clearLightbringerCounts() { 245 lightbringerButtonAppearInCollapsedCallLogItemCount = 0; 246 lightbringerButtonAppearInExpandedCallLogItemCount = 0; 247 lightbringerButtonAppearInSearchCount = 0; 248 } 249 250 @Override describeContents()251 public int describeContents() { 252 return 0; 253 } 254 255 @Override writeToParcel(Parcel dest, int flags)256 public void writeToParcel(Parcel dest, int flags) { 257 dest.writeParcelable(number(), flags); 258 dest.writeByteArray(callSpecificAppData().toByteArray()); 259 dest.writeParcelable(phoneAccountHandle(), flags); 260 dest.writeInt(isVideoCall() ? 1 : 0); 261 dest.writeString(callSubject()); 262 dest.writeInt(allowAssistedDial() ? 1 : 0); 263 Bundle stringInCallUiIntentExtrasBundle = new Bundle(); 264 stringInCallUiIntentExtras().forEach(stringInCallUiIntentExtrasBundle::putString); 265 dest.writeBundle(stringInCallUiIntentExtrasBundle); 266 Bundle longInCallUiIntentExtrasBundle = new Bundle(); 267 longInCallUiIntentExtras().forEach(longInCallUiIntentExtrasBundle::putLong); 268 dest.writeBundle(longInCallUiIntentExtrasBundle); 269 } 270 271 // @TODO(justinmcclain): Investigate deleting the parcelable logic and instead switching 272 // to using an internal proto for serialization. 273 public static final Creator<CallIntent> CREATOR = 274 new Creator<CallIntent>() { 275 @Override 276 public CallIntent createFromParcel(Parcel source) { 277 CallIntent.Builder callIntentBuilder = builder(); 278 ClassLoader classLoader = CallIntent.class.getClassLoader(); 279 callIntentBuilder.setNumber(source.readParcelable(classLoader)); 280 CallSpecificAppData data; 281 try { 282 data = CallSpecificAppData.parseFrom(source.createByteArray()); 283 } catch (InvalidProtocolBufferException e) { 284 data = CallSpecificAppData.getDefaultInstance(); 285 } 286 callIntentBuilder 287 .setCallSpecificAppData(data) 288 .setPhoneAccountHandle(source.readParcelable(classLoader)) 289 .setIsVideoCall(source.readInt() != 0) 290 .setCallSubject(source.readString()) 291 .setAllowAssistedDial(source.readInt() != 0); 292 Bundle stringInCallUiIntentExtrasBundle = source.readBundle(classLoader); 293 for (String key : stringInCallUiIntentExtrasBundle.keySet()) { 294 callIntentBuilder.addInCallUiIntentExtra( 295 key, stringInCallUiIntentExtrasBundle.getString(key)); 296 } 297 Bundle longInCallUiIntentExtrasBundle = source.readBundle(classLoader); 298 for (String key : longInCallUiIntentExtrasBundle.keySet()) { 299 callIntentBuilder.addInCallUiIntentExtra( 300 key, longInCallUiIntentExtrasBundle.getLong(key)); 301 } 302 return callIntentBuilder.autoBuild(); 303 } 304 305 @Override 306 public CallIntent[] newArray(int size) { 307 return new CallIntent[0]; 308 } 309 }; 310 } 311