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