• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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 android.telecom;
18 
19 import static android.Manifest.permission.MODIFY_PHONE_STATE;
20 
21 import android.annotation.NonNull;
22 import android.annotation.RequiresPermission;
23 import android.annotation.SystemApi;
24 import android.content.Intent;
25 import android.graphics.drawable.Icon;
26 import android.net.Uri;
27 import android.os.Bundle;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 import android.telephony.CarrierConfigManager;
31 import android.telephony.TelephonyManager;
32 import android.text.TextUtils;
33 
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.List;
37 import java.util.Objects;
38 
39 /**
40  * Represents a distinct method to place or receive a phone call. Apps which can place calls and
41  * want those calls to be integrated into the dialer and in-call UI should build an instance of
42  * this class and register it with the system using {@link TelecomManager}.
43  * <p>
44  * {@link TelecomManager} uses registered {@link PhoneAccount}s to present the user with
45  * alternative options when placing a phone call. When building a {@link PhoneAccount}, the app
46  * should supply a valid {@link PhoneAccountHandle} that references the connection service
47  * implementation Telecom will use to interact with the app.
48  */
49 public final class PhoneAccount implements Parcelable {
50 
51     /**
52      * Integer extra which determines the order in which {@link PhoneAccount}s are sorted
53      *
54      * This is an extras key set via {@link Builder#setExtras} which determines the order in which
55      * {@link PhoneAccount}s from the same {@link ConnectionService} are sorted. The accounts
56      * are sorted in ascending order by this key, and this ordering is used to
57      * determine priority when a call can be placed via multiple accounts.
58      *
59      * When multiple {@link PhoneAccount}s are supplied with the same sort order key, no ordering is
60      * guaranteed between those {@link PhoneAccount}s. Additionally, no ordering is guaranteed
61      * between {@link PhoneAccount}s that do not supply this extra, and all such accounts
62      * will be sorted after the accounts that do supply this extra.
63      *
64      * An example of a sort order key is slot index (see {@link TelephonyManager#getSlotIndex()}),
65      * which is the one used by the cell Telephony stack.
66      * @hide
67      */
68     @SystemApi
69     public static final String EXTRA_SORT_ORDER =
70             "android.telecom.extra.SORT_ORDER";
71 
72     /**
73      * {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which determines the
74      * maximum permitted length of a call subject specified via the
75      * {@link TelecomManager#EXTRA_CALL_SUBJECT} extra on an
76      * {@link android.content.Intent#ACTION_CALL} intent.  Ultimately a {@link ConnectionService} is
77      * responsible for enforcing the maximum call subject length when sending the message, however
78      * this extra is provided so that the user interface can proactively limit the length of the
79      * call subject as the user types it.
80      */
81     public static final String EXTRA_CALL_SUBJECT_MAX_LENGTH =
82             "android.telecom.extra.CALL_SUBJECT_MAX_LENGTH";
83 
84     /**
85      * {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which determines the
86      * character encoding to be used when determining the length of messages.
87      * The user interface can use this when determining the number of characters the user may type
88      * in a call subject.  If empty-string, the call subject message size limit will be enforced on
89      * a 1:1 basis.  That is, each character will count towards the messages size limit as a single
90      * character.  If a character encoding is specified, the message size limit will be based on the
91      * number of bytes in the message per the specified encoding.  See
92      * {@link #EXTRA_CALL_SUBJECT_MAX_LENGTH} for more information on the call subject maximum
93      * length.
94      */
95     public static final String EXTRA_CALL_SUBJECT_CHARACTER_ENCODING =
96             "android.telecom.extra.CALL_SUBJECT_CHARACTER_ENCODING";
97 
98     /**
99      * Boolean {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which
100      * indicates that all calls from this {@link PhoneAccount} should be treated as VoIP calls
101      * rather than cellular calls by the Telecom audio handling logic.
102      */
103     public static final String EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE =
104             "android.telecom.extra.ALWAYS_USE_VOIP_AUDIO_MODE";
105 
106     /**
107      * Boolean {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which
108      * indicates whether this {@link PhoneAccount} is capable of supporting a request to handover a
109      * connection (see {@code android.telecom.Call#handoverTo()}) to this {@link PhoneAccount} from
110      * a {@link PhoneAccount} specifying {@link #EXTRA_SUPPORTS_HANDOVER_FROM}.
111      * <p>
112      * A handover request is initiated by the user from the default dialer app to indicate a desire
113      * to handover a call from one {@link PhoneAccount}/{@link ConnectionService} to another.
114      */
115     public static final String EXTRA_SUPPORTS_HANDOVER_TO =
116             "android.telecom.extra.SUPPORTS_HANDOVER_TO";
117 
118     /**
119      * Boolean {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which
120      * indicates whether this {@link PhoneAccount} supports using a fallback if video calling is
121      * not available. This extra is for device level support, {@link
122      * android.telephony.CarrierConfigManager#KEY_ALLOW_VIDEO_CALLING_FALLBACK_BOOL} should also
123      * be checked to ensure it is not disabled by individual carrier.
124      *
125      * @hide
126      */
127     public static final String EXTRA_SUPPORTS_VIDEO_CALLING_FALLBACK =
128             "android.telecom.extra.SUPPORTS_VIDEO_CALLING_FALLBACK";
129 
130     /**
131      * Boolean {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which
132      * indicates whether this {@link PhoneAccount} is capable of supporting a request to handover a
133      * connection from this {@link PhoneAccount} to another {@link PhoneAccount}.
134      * (see {@code android.telecom.Call#handoverTo()}) which specifies
135      * {@link #EXTRA_SUPPORTS_HANDOVER_TO}.
136      * <p>
137      * A handover request is initiated by the user from the default dialer app to indicate a desire
138      * to handover a call from one {@link PhoneAccount}/{@link ConnectionService} to another.
139      */
140     public static final String EXTRA_SUPPORTS_HANDOVER_FROM =
141             "android.telecom.extra.SUPPORTS_HANDOVER_FROM";
142 
143 
144     /**
145      * Boolean {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which
146      * indicates whether a Self-Managed {@link PhoneAccount} should log its calls to the call log.
147      * Self-Managed {@link PhoneAccount}s are responsible for their own notifications, so the system
148      * will not create a notification when a missed call is logged.
149      * <p>
150      * By default, Self-Managed {@link PhoneAccount}s do not log their calls to the call log.
151      * Setting this extra to {@code true} provides a means for them to log their calls.
152      * <p>
153      * Note: Only calls where the {@link Call.Details#getHandle()} {@link Uri#getScheme()} is
154      * {@link #SCHEME_SIP} or {@link #SCHEME_TEL} will be logged at the current time.
155      */
156     public static final String EXTRA_LOG_SELF_MANAGED_CALLS =
157             "android.telecom.extra.LOG_SELF_MANAGED_CALLS";
158 
159     /**
160      * Boolean {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which
161      * indicates whether calls for a {@link PhoneAccount} should generate a "call recording tone"
162      * when the user is recording audio on the device.
163      * <p>
164      * The call recording tone is played over the telephony audio stream so that the remote party
165      * has an audible indication that it is possible their call is being recorded using a call
166      * recording app on the device.
167      * <p>
168      * This extra only has an effect for calls placed via Telephony (e.g.
169      * {@link #CAPABILITY_SIM_SUBSCRIPTION}).
170      * <p>
171      * The call recording tone is a 1400 hz tone which repeats every 15 seconds while recording is
172      * in progress.
173      * @hide
174      */
175     @SystemApi
176     public static final String EXTRA_PLAY_CALL_RECORDING_TONE =
177             "android.telecom.extra.PLAY_CALL_RECORDING_TONE";
178 
179     /**
180      * Boolean {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()} which
181      * indicates whether calls for a {@link PhoneAccount} should skip call filtering.
182      * <p>
183      * If not specified, this will default to false; all calls will undergo call filtering unless
184      * specifically exempted (e.g. {@link Connection#PROPERTY_EMERGENCY_CALLBACK_MODE}.) However,
185      * this may be used to skip call filtering when it has already been performed on another device.
186      * @hide
187      */
188     public static final String EXTRA_SKIP_CALL_FILTERING =
189         "android.telecom.extra.SKIP_CALL_FILTERING";
190 
191     /**
192      * Boolean {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which
193      * indicates whether a Self-managed {@link PhoneAccount} want to expose its calls to all
194      * {@link InCallService} which declares the metadata
195      * {@link TelecomManager#METADATA_INCLUDE_SELF_MANAGED_CALLS}.
196      */
197     public static final String EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE =
198             "android.telecom.extra.ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE";
199 
200     /**
201      * Flag indicating that this {@code PhoneAccount} can act as a connection manager for
202      * other connections. The {@link ConnectionService} associated with this {@code PhoneAccount}
203      * will be allowed to manage phone calls including using its own proprietary phone-call
204      * implementation (like VoIP calling) to make calls instead of the telephony stack.
205      * <p>
206      * When a user opts to place a call using the SIM-based telephony stack, the
207      * {@link ConnectionService} associated with this {@code PhoneAccount} will be attempted first
208      * if the user has explicitly selected it to be used as the default connection manager.
209      * <p>
210      * See {@link #getCapabilities}
211      */
212     public static final int CAPABILITY_CONNECTION_MANAGER = 0x1;
213 
214     /**
215      * Flag indicating that this {@code PhoneAccount} can make phone calls in place of
216      * traditional SIM-based telephony calls. This account will be treated as a distinct method
217      * for placing calls alongside the traditional SIM-based telephony stack. This flag is
218      * distinct from {@link #CAPABILITY_CONNECTION_MANAGER} in that it is not allowed to manage
219      * or place calls from the built-in telephony stack.
220      * <p>
221      * See {@link #getCapabilities}
222      * <p>
223      */
224     public static final int CAPABILITY_CALL_PROVIDER = 0x2;
225 
226     /**
227      * Flag indicating that this {@code PhoneAccount} represents a built-in PSTN SIM
228      * subscription.
229      * <p>
230      * Only the Android framework can register a {@code PhoneAccount} having this capability.
231      * <p>
232      * See {@link #getCapabilities}
233      */
234     public static final int CAPABILITY_SIM_SUBSCRIPTION = 0x4;
235 
236     /**
237      * Flag indicating that this {@code PhoneAccount} is currently able to place video calls.
238      * <p>
239      * See also {@link #CAPABILITY_SUPPORTS_VIDEO_CALLING} which indicates whether the
240      * {@code PhoneAccount} supports placing video calls.
241      * <p>
242      * See {@link #getCapabilities}
243      */
244     public static final int CAPABILITY_VIDEO_CALLING = 0x8;
245 
246     /**
247      * Flag indicating that this {@code PhoneAccount} is capable of placing emergency calls.
248      * By default all PSTN {@code PhoneAccount}s are capable of placing emergency calls.
249      * <p>
250      * See {@link #getCapabilities}
251      */
252     public static final int CAPABILITY_PLACE_EMERGENCY_CALLS = 0x10;
253 
254     /**
255      * Flag indicating that this {@code PhoneAccount} is capable of being used by all users. This
256      * should only be used by system apps (and will be ignored for all other apps trying to use it).
257      * <p>
258      * See {@link #getCapabilities}
259      * @hide
260      */
261     @SystemApi
262     public static final int CAPABILITY_MULTI_USER = 0x20;
263 
264     /**
265      * Flag indicating that this {@code PhoneAccount} supports a subject for Calls.  This means a
266      * caller is able to specify a short subject line for an outgoing call.  A capable receiving
267      * device displays the call subject on the incoming call screen.
268      * <p>
269      * See {@link #getCapabilities}
270      */
271     public static final int CAPABILITY_CALL_SUBJECT = 0x40;
272 
273     /**
274      * Flag indicating that this {@code PhoneAccount} should only be used for emergency calls.
275      * <p>
276      * See {@link #getCapabilities}
277      * @hide
278      */
279     @SystemApi
280     public static final int CAPABILITY_EMERGENCY_CALLS_ONLY = 0x80;
281 
282     /**
283      * Flag indicating that for this {@code PhoneAccount}, the ability to make a video call to a
284      * number relies on presence.  Should only be set if the {@code PhoneAccount} also has
285      * {@link #CAPABILITY_VIDEO_CALLING}.
286      * <p>
287      * Note: As of Android 12, using the
288      * {@link android.provider.ContactsContract.Data#CARRIER_PRESENCE_VT_CAPABLE} bit on the
289      * {@link android.provider.ContactsContract.Data#CARRIER_PRESENCE} column to indicate whether
290      * a contact's phone number supports video calling has been deprecated and should only be used
291      * on devices where {@link CarrierConfigManager#KEY_USE_RCS_PRESENCE_BOOL} is set. On newer
292      * devices, applications must use {@link android.telephony.ims.RcsUceAdapter} instead to
293      * determine whether or not a contact's phone number supports carrier video calling.
294      * <p>
295      * See {@link #getCapabilities}
296      */
297     public static final int CAPABILITY_VIDEO_CALLING_RELIES_ON_PRESENCE = 0x100;
298 
299     /**
300      * Flag indicating that for this {@link PhoneAccount}, emergency video calling is allowed.
301      * <p>
302      * When set, Telecom will allow emergency video calls to be placed.  When not set, Telecom will
303      * convert all outgoing video calls to emergency numbers to audio-only.
304      * @hide
305      */
306     @SystemApi
307     public static final int CAPABILITY_EMERGENCY_VIDEO_CALLING = 0x200;
308 
309     /**
310      * Flag indicating that this {@link PhoneAccount} supports video calling.
311      * This is not an indication that the {@link PhoneAccount} is currently able to make a video
312      * call, but rather that it has the ability to make video calls (but not necessarily at this
313      * time).
314      * <p>
315      * Whether a {@link PhoneAccount} can make a video call is ultimately controlled by
316      * {@link #CAPABILITY_VIDEO_CALLING}, which indicates whether the {@link PhoneAccount} is
317      * currently capable of making a video call.  Consider a case where, for example, a
318      * {@link PhoneAccount} supports making video calls (e.g.
319      * {@link #CAPABILITY_SUPPORTS_VIDEO_CALLING}), but a current lack of network connectivity
320      * prevents video calls from being made (e.g. {@link #CAPABILITY_VIDEO_CALLING}).
321      * <p>
322      * See {@link #getCapabilities}
323      */
324     public static final int CAPABILITY_SUPPORTS_VIDEO_CALLING = 0x400;
325 
326     /**
327      * Flag indicating that this {@link PhoneAccount} is responsible for managing its own
328      * {@link Connection}s.  This type of {@link PhoneAccount} is ideal for use with standalone
329      * calling apps which do not wish to use the default phone app for {@link Connection} UX,
330      * but which want to leverage the call and audio routing capabilities of the Telecom framework.
331      * <p>
332      * When set, {@link Connection}s created by the self-managed {@link ConnectionService} will not
333      * be surfaced to implementations of the {@link InCallService} API.  Thus it is the
334      * responsibility of a self-managed {@link ConnectionService} to provide a user interface for
335      * its {@link Connection}s.
336      * <p>
337      * Self-managed {@link Connection}s will, however, be displayed on connected Bluetooth devices.
338      */
339     public static final int CAPABILITY_SELF_MANAGED = 0x800;
340 
341     /**
342      * Flag indicating that this {@link PhoneAccount} is capable of making a call with an
343      * RTT (Real-time text) session.
344      * When set, Telecom will attempt to open an RTT session on outgoing calls that specify
345      * that they should be placed with an RTT session , and the in-call app will be displayed
346      * with text entry fields for RTT. Likewise, the in-call app can request that an RTT
347      * session be opened during a call if this bit is set.
348      */
349     public static final int CAPABILITY_RTT = 0x1000;
350 
351     /**
352      * Flag indicating that this {@link PhoneAccount} is the preferred SIM subscription for
353      * emergency calls. A {@link PhoneAccount} that sets this capability must also
354      * set the {@link #CAPABILITY_SIM_SUBSCRIPTION} and {@link #CAPABILITY_PLACE_EMERGENCY_CALLS}
355      * capabilities. There must only be one emergency preferred {@link PhoneAccount} on the device.
356      * <p>
357      * When set, Telecom will prefer this {@link PhoneAccount} over others for emergency calling,
358      * even if the emergency call was placed with a specific {@link PhoneAccount} set using the
359      * extra{@link TelecomManager#EXTRA_PHONE_ACCOUNT_HANDLE} in
360      * {@link Intent#ACTION_CALL_EMERGENCY} or {@link TelecomManager#placeCall(Uri, Bundle)}.
361      *
362      * @hide
363      */
364     @SystemApi
365     public static final int CAPABILITY_EMERGENCY_PREFERRED = 0x2000;
366 
367     /**
368      * An adhoc conference call is established by providing a list of addresses to
369      * {@code TelecomManager#startConference(List<Uri>, int videoState)} where the
370      * {@link ConnectionService} is responsible for connecting all indicated participants
371      * to a conference simultaneously.
372      * This is in contrast to conferences formed by merging calls together (e.g. using
373      * {@link android.telecom.Call#mergeConference()}).
374      */
375     public static final int CAPABILITY_ADHOC_CONFERENCE_CALLING = 0x4000;
376 
377     /**
378      * Flag indicating whether this {@link PhoneAccount} is capable of supporting the call composer
379      * functionality for enriched calls.
380      */
381     public static final int CAPABILITY_CALL_COMPOSER = 0x8000;
382 
383     /**
384      * Flag indicating that this {@link PhoneAccount} provides SIM-based voice calls, potentially as
385      * an over-the-top solution such as wi-fi calling.
386      *
387      * <p>Similar to {@link #CAPABILITY_SUPPORTS_VIDEO_CALLING}, this capability indicates this
388      * {@link PhoneAccount} has the ability to make voice calls (but not necessarily at this time).
389      * Whether this {@link PhoneAccount} can make a voice call is ultimately controlled by {@link
390      * #CAPABILITY_VOICE_CALLING_AVAILABLE}, which indicates whether this {@link PhoneAccount} is
391      * currently capable of making a voice call. Consider a case where, for example, a {@link
392      * PhoneAccount} supports making voice calls (e.g. {@link
393      * #CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS}), but a current lack of network connectivity
394      * prevents voice calls from being made (e.g. {@link #CAPABILITY_VOICE_CALLING_AVAILABLE}).
395      *
396      * <p>In order to declare this capability, this {@link PhoneAccount} must also declare {@link
397      * #CAPABILITY_SIM_SUBSCRIPTION} or {@link #CAPABILITY_CONNECTION_MANAGER} and satisfy the
398      * associated requirements.
399      *
400      * @see #CAPABILITY_VOICE_CALLING_AVAILABLE
401      * @see #getCapabilities
402      */
403     public static final int CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS = 0x10000;
404 
405     /**
406      * Flag indicating that this {@link PhoneAccount} is <em>currently</em> able to place SIM-based
407      * voice calls, similar to {@link #CAPABILITY_VIDEO_CALLING}.
408      *
409      * <p>See also {@link #CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS}, which indicates whether
410      * the {@code PhoneAccount} supports placing SIM-based voice calls or not.
411      *
412      * <p>In order to declare this capability, this {@link PhoneAccount} must also declare {@link
413      * #CAPABILITY_SIM_SUBSCRIPTION} or {@link #CAPABILITY_CONNECTION_MANAGER} and satisfy the
414      * associated requirements.
415      *
416      * @see #CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS
417      * @see #getCapabilities
418      */
419     public static final int CAPABILITY_VOICE_CALLING_AVAILABLE = 0x20000;
420 
421     /* NEXT CAPABILITY: 0x40000 */
422 
423     /**
424      * URI scheme for telephone number URIs.
425      */
426     public static final String SCHEME_TEL = "tel";
427 
428     /**
429      * URI scheme for voicemail URIs.
430      */
431     public static final String SCHEME_VOICEMAIL = "voicemail";
432 
433     /**
434      * URI scheme for SIP URIs.
435      */
436     public static final String SCHEME_SIP = "sip";
437 
438     /**
439      * Indicating no icon tint is set.
440      * @hide
441      */
442     public static final int NO_ICON_TINT = 0;
443 
444     /**
445      * Indicating no hightlight color is set.
446      */
447     public static final int NO_HIGHLIGHT_COLOR = 0;
448 
449     /**
450      * Indicating no resource ID is set.
451      */
452     public static final int NO_RESOURCE_ID = -1;
453 
454     private final PhoneAccountHandle mAccountHandle;
455     private final Uri mAddress;
456     private final Uri mSubscriptionAddress;
457     private final int mCapabilities;
458     private final int mHighlightColor;
459     private final CharSequence mLabel;
460     private final CharSequence mShortDescription;
461     private final List<String> mSupportedUriSchemes;
462     private final int mSupportedAudioRoutes;
463     private final Icon mIcon;
464     private final Bundle mExtras;
465     private boolean mIsEnabled;
466     private String mGroupId;
467 
468     @Override
equals(Object o)469     public boolean equals(Object o) {
470         if (this == o) return true;
471         if (o == null || getClass() != o.getClass()) return false;
472         PhoneAccount that = (PhoneAccount) o;
473         return mCapabilities == that.mCapabilities &&
474                 mHighlightColor == that.mHighlightColor &&
475                 mSupportedAudioRoutes == that.mSupportedAudioRoutes &&
476                 mIsEnabled == that.mIsEnabled &&
477                 Objects.equals(mAccountHandle, that.mAccountHandle) &&
478                 Objects.equals(mAddress, that.mAddress) &&
479                 Objects.equals(mSubscriptionAddress, that.mSubscriptionAddress) &&
480                 Objects.equals(mLabel, that.mLabel) &&
481                 Objects.equals(mShortDescription, that.mShortDescription) &&
482                 Objects.equals(mSupportedUriSchemes, that.mSupportedUriSchemes) &&
483                 areBundlesEqual(mExtras, that.mExtras) &&
484                 Objects.equals(mGroupId, that.mGroupId);
485     }
486 
487     @Override
hashCode()488     public int hashCode() {
489         return Objects.hash(mAccountHandle, mAddress, mSubscriptionAddress, mCapabilities,
490                 mHighlightColor, mLabel, mShortDescription, mSupportedUriSchemes,
491                 mSupportedAudioRoutes,
492                 mExtras, mIsEnabled, mGroupId);
493     }
494 
495     /**
496      * Helper class for creating a {@link PhoneAccount}.
497      */
498     public static class Builder {
499 
500         private PhoneAccountHandle mAccountHandle;
501         private Uri mAddress;
502         private Uri mSubscriptionAddress;
503         private int mCapabilities;
504         private int mSupportedAudioRoutes = CallAudioState.ROUTE_ALL;
505         private int mHighlightColor = NO_HIGHLIGHT_COLOR;
506         private CharSequence mLabel;
507         private CharSequence mShortDescription;
508         private List<String> mSupportedUriSchemes = new ArrayList<String>();
509         private Icon mIcon;
510         private Bundle mExtras;
511         private boolean mIsEnabled = false;
512         private String mGroupId = "";
513 
514         /**
515          * Creates a builder with the specified {@link PhoneAccountHandle} and label.
516          */
Builder(PhoneAccountHandle accountHandle, CharSequence label)517         public Builder(PhoneAccountHandle accountHandle, CharSequence label) {
518             this.mAccountHandle = accountHandle;
519             this.mLabel = label;
520         }
521 
522         /**
523          * Creates an instance of the {@link PhoneAccount.Builder} from an existing
524          * {@link PhoneAccount}.
525          *
526          * @param phoneAccount The {@link PhoneAccount} used to initialize the builder.
527          */
Builder(PhoneAccount phoneAccount)528         public Builder(PhoneAccount phoneAccount) {
529             mAccountHandle = phoneAccount.getAccountHandle();
530             mAddress = phoneAccount.getAddress();
531             mSubscriptionAddress = phoneAccount.getSubscriptionAddress();
532             mCapabilities = phoneAccount.getCapabilities();
533             mHighlightColor = phoneAccount.getHighlightColor();
534             mLabel = phoneAccount.getLabel();
535             mShortDescription = phoneAccount.getShortDescription();
536             mSupportedUriSchemes.addAll(phoneAccount.getSupportedUriSchemes());
537             mIcon = phoneAccount.getIcon();
538             mIsEnabled = phoneAccount.isEnabled();
539             mExtras = phoneAccount.getExtras();
540             mGroupId = phoneAccount.getGroupId();
541             mSupportedAudioRoutes = phoneAccount.getSupportedAudioRoutes();
542         }
543 
544         /**
545          * Sets the label. See {@link PhoneAccount#getLabel()}.
546          *
547          * @param label The label of the phone account.
548          * @return The builder.
549          * @hide
550          */
setLabel(CharSequence label)551         public Builder setLabel(CharSequence label) {
552             this.mLabel = label;
553             return this;
554         }
555 
556         /**
557          * Sets the address. See {@link PhoneAccount#getAddress}.
558          * <p>
559          * Note: The entire URI value is limited to 256 characters. This check is
560          * enforced when registering the PhoneAccount via
561          * {@link TelecomManager#registerPhoneAccount(PhoneAccount)} and will cause an
562          * {@link IllegalArgumentException} to be thrown if URI is over 256.
563          *
564          * @param value The address of the phone account.
565          * @return The builder.
566          */
setAddress(Uri value)567         public Builder setAddress(Uri value) {
568             this.mAddress = value;
569             return this;
570         }
571 
572         /**
573          * Sets the subscription address. See {@link PhoneAccount#getSubscriptionAddress}.
574          *
575          * @param value The subscription address.
576          * @return The builder.
577          */
setSubscriptionAddress(Uri value)578         public Builder setSubscriptionAddress(Uri value) {
579             this.mSubscriptionAddress = value;
580             return this;
581         }
582 
583         /**
584          * Sets the capabilities. See {@link PhoneAccount#getCapabilities}.
585          *
586          * @param value The capabilities to set.
587          * @return The builder.
588          */
setCapabilities(int value)589         public Builder setCapabilities(int value) {
590             this.mCapabilities = value;
591             return this;
592         }
593 
594         /**
595          * Sets the icon. See {@link PhoneAccount#getIcon}.
596          * <p>
597          * Note: An {@link IllegalArgumentException} if the Icon cannot be written to memory.
598          * This check is enforced when registering the PhoneAccount via
599          * {@link TelecomManager#registerPhoneAccount(PhoneAccount)}
600          *
601          * @param icon The icon to set.
602          */
setIcon(Icon icon)603         public Builder setIcon(Icon icon) {
604             mIcon = icon;
605             return this;
606         }
607 
608         /**
609          * Sets the highlight color. See {@link PhoneAccount#getHighlightColor}.
610          *
611          * @param value The highlight color.
612          * @return The builder.
613          */
setHighlightColor(int value)614         public Builder setHighlightColor(int value) {
615             this.mHighlightColor = value;
616             return this;
617         }
618 
619         /**
620          * Sets the short description. See {@link PhoneAccount#getShortDescription}.
621          *
622          * @param value The short description.
623          * @return The builder.
624          */
setShortDescription(CharSequence value)625         public Builder setShortDescription(CharSequence value) {
626             this.mShortDescription = value;
627             return this;
628         }
629 
630         /**
631          * Specifies an additional URI scheme supported by the {@link PhoneAccount}.
632          *
633          * <p>
634          * Each URI scheme is limited to 256 characters.  Adding a scheme over 256 characters will
635          * cause an {@link IllegalArgumentException} to be thrown when the account is registered.
636          *
637          * @param uriScheme The URI scheme.
638          * @return The builder.
639          */
addSupportedUriScheme(String uriScheme)640         public Builder addSupportedUriScheme(String uriScheme) {
641             if (!TextUtils.isEmpty(uriScheme) && !mSupportedUriSchemes.contains(uriScheme)) {
642                 this.mSupportedUriSchemes.add(uriScheme);
643             }
644             return this;
645         }
646 
647         /**
648          * Specifies the URI schemes supported by the {@link PhoneAccount}.
649          *
650          * <p>
651          * A max of 10 URI schemes can be added per account.  Additionally, each URI scheme is
652          * limited to 256 characters. Adding more than 10 URI schemes or 256 characters on any
653          * scheme will cause an {@link IllegalArgumentException} to be thrown when the account
654          * is registered.
655          *
656          * @param uriSchemes The URI schemes.
657          * @return The builder.
658          */
setSupportedUriSchemes(List<String> uriSchemes)659         public Builder setSupportedUriSchemes(List<String> uriSchemes) {
660             mSupportedUriSchemes.clear();
661 
662             if (uriSchemes != null && !uriSchemes.isEmpty()) {
663                 for (String uriScheme : uriSchemes) {
664                     addSupportedUriScheme(uriScheme);
665                 }
666             }
667             return this;
668         }
669 
670         /**
671          * Specifies the extras associated with the {@link PhoneAccount}.
672          * <p>
673          * {@code PhoneAccount}s only support extra values of type: {@link String}, {@link Integer},
674          * and {@link Boolean}.  Extras which are not of these types are ignored.
675          *
676          * @param extras
677          * @return
678          */
setExtras(Bundle extras)679         public Builder setExtras(Bundle extras) {
680             mExtras = extras;
681             return this;
682         }
683 
684         /**
685          * Sets the enabled state of the phone account.
686          *
687          * @param isEnabled The enabled state.
688          * @return The builder.
689          * @hide
690          */
setIsEnabled(boolean isEnabled)691         public Builder setIsEnabled(boolean isEnabled) {
692             mIsEnabled = isEnabled;
693             return this;
694         }
695 
696         /**
697          * Sets the group Id of the {@link PhoneAccount}. When a new {@link PhoneAccount} is
698          * registered to Telecom, it will replace another {@link PhoneAccount} that is already
699          * registered in Telecom and take on the current user defaults and enabled status. There can
700          * only be one {@link PhoneAccount} with a non-empty group number registered to Telecom at a
701          * time. By default, there is no group Id for a {@link PhoneAccount} (an empty String). Only
702          * grouped {@link PhoneAccount}s with the same {@link ConnectionService} can be replaced.
703          * <p>
704          * Note: This is an API specific to the Telephony stack; the group Id will be ignored for
705          * callers not holding the correct permission.
706          *
707          * @param groupId The group Id of the {@link PhoneAccount} that will replace any other
708          * registered {@link PhoneAccount} in Telecom with the same Group Id.
709          * @return The builder
710          * @hide
711          */
712         @SystemApi
713         @RequiresPermission(MODIFY_PHONE_STATE)
setGroupId(@onNull String groupId)714         public @NonNull Builder setGroupId(@NonNull String groupId) {
715             if (groupId != null) {
716                 mGroupId = groupId;
717             } else {
718                 mGroupId = "";
719             }
720             return this;
721         }
722 
723         /**
724          * Sets the audio routes supported by this {@link PhoneAccount}.
725          *
726          * @param routes bit mask of available routes.
727          * @return The builder.
728          * @hide
729          */
setSupportedAudioRoutes(int routes)730         public Builder setSupportedAudioRoutes(int routes) {
731             mSupportedAudioRoutes = routes;
732             return this;
733         }
734 
735         /**
736          * Creates an instance of a {@link PhoneAccount} based on the current builder settings.
737          *
738          * @return The {@link PhoneAccount}.
739          */
build()740         public PhoneAccount build() {
741             // If no supported URI schemes were defined, assume "tel" is supported.
742             if (mSupportedUriSchemes.isEmpty()) {
743                 addSupportedUriScheme(SCHEME_TEL);
744             }
745 
746             return new PhoneAccount(
747                     mAccountHandle,
748                     mAddress,
749                     mSubscriptionAddress,
750                     mCapabilities,
751                     mIcon,
752                     mHighlightColor,
753                     mLabel,
754                     mShortDescription,
755                     mSupportedUriSchemes,
756                     mExtras,
757                     mSupportedAudioRoutes,
758                     mIsEnabled,
759                     mGroupId);
760         }
761     }
762 
PhoneAccount( PhoneAccountHandle account, Uri address, Uri subscriptionAddress, int capabilities, Icon icon, int highlightColor, CharSequence label, CharSequence shortDescription, List<String> supportedUriSchemes, Bundle extras, int supportedAudioRoutes, boolean isEnabled, String groupId)763     private PhoneAccount(
764             PhoneAccountHandle account,
765             Uri address,
766             Uri subscriptionAddress,
767             int capabilities,
768             Icon icon,
769             int highlightColor,
770             CharSequence label,
771             CharSequence shortDescription,
772             List<String> supportedUriSchemes,
773             Bundle extras,
774             int supportedAudioRoutes,
775             boolean isEnabled,
776             String groupId) {
777         mAccountHandle = account;
778         mAddress = address;
779         mSubscriptionAddress = subscriptionAddress;
780         mCapabilities = capabilities;
781         mIcon = icon;
782         mHighlightColor = highlightColor;
783         mLabel = label;
784         mShortDescription = shortDescription;
785         mSupportedUriSchemes = Collections.unmodifiableList(supportedUriSchemes);
786         mExtras = extras;
787         mSupportedAudioRoutes = supportedAudioRoutes;
788         mIsEnabled = isEnabled;
789         mGroupId = groupId;
790     }
791 
builder( PhoneAccountHandle accountHandle, CharSequence label)792     public static Builder builder(
793             PhoneAccountHandle accountHandle,
794             CharSequence label) {
795         return new Builder(accountHandle, label);
796     }
797 
798     /**
799      * Returns a builder initialized with the current {@link PhoneAccount} instance.
800      *
801      * @return The builder.
802      */
toBuilder()803     public Builder toBuilder() { return new Builder(this); }
804 
805     /**
806      * The unique identifier of this {@code PhoneAccount}.
807      *
808      * @return A {@code PhoneAccountHandle}.
809      */
getAccountHandle()810     public PhoneAccountHandle getAccountHandle() {
811         return mAccountHandle;
812     }
813 
814     /**
815      * The address (e.g., a phone number) associated with this {@code PhoneAccount}. This
816      * represents the destination from which outgoing calls using this {@code PhoneAccount}
817      * will appear to come, if applicable, and the destination to which incoming calls using this
818      * {@code PhoneAccount} may be addressed.
819      *
820      * @return A address expressed as a {@code Uri}, for example, a phone number.
821      */
getAddress()822     public Uri getAddress() {
823         return mAddress;
824     }
825 
826     /**
827      * The raw callback number used for this {@code PhoneAccount}, as distinct from
828      * {@link #getAddress()}. For the majority of {@code PhoneAccount}s this should be registered
829      * as {@code null}.  It is used by the system for SIM-based {@code PhoneAccount} registration
830      * where {@link android.telephony.TelephonyManager#setLine1NumberForDisplay(String, String)}
831      * has been used to alter the callback number.
832      * <p>
833      *
834      * @return The subscription number, suitable for display to the user.
835      */
getSubscriptionAddress()836     public Uri getSubscriptionAddress() {
837         return mSubscriptionAddress;
838     }
839 
840     /**
841      * The capabilities of this {@code PhoneAccount}.
842      *
843      * @return A bit field of flags describing this {@code PhoneAccount}'s capabilities.
844      */
getCapabilities()845     public int getCapabilities() {
846         return mCapabilities;
847     }
848 
849     /**
850      * Determines if this {@code PhoneAccount} has a capabilities specified by the passed in
851      * bit mask.
852      *
853      * @param capability The capabilities to check.
854      * @return {@code true} if the phone account has the capability.
855      */
hasCapabilities(int capability)856     public boolean hasCapabilities(int capability) {
857         return (mCapabilities & capability) == capability;
858     }
859 
860     /**
861      * Determines if this {@code PhoneAccount} has routes specified by the passed in bit mask.
862      *
863      * @param route The routes to check.
864      * @return {@code true} if the phone account has the routes.
865      * @hide
866      */
hasAudioRoutes(int routes)867     public boolean hasAudioRoutes(int routes) {
868         return (mSupportedAudioRoutes & routes) == routes;
869     }
870 
871     /**
872      * A short label describing a {@code PhoneAccount}.
873      *
874      * @return A label for this {@code PhoneAccount}.
875      */
getLabel()876     public CharSequence getLabel() {
877         return mLabel;
878     }
879 
880     /**
881      * A short paragraph describing this {@code PhoneAccount}.
882      *
883      * @return A description for this {@code PhoneAccount}.
884      */
getShortDescription()885     public CharSequence getShortDescription() {
886         return mShortDescription;
887     }
888 
889     /**
890      * The URI schemes supported by this {@code PhoneAccount}.
891      *
892      * @return The URI schemes.
893      */
getSupportedUriSchemes()894     public List<String> getSupportedUriSchemes() {
895         return mSupportedUriSchemes;
896     }
897 
898     /**
899      * The extras associated with this {@code PhoneAccount}.
900      * <p>
901      * A {@link ConnectionService} may provide implementation specific information about the
902      * {@link PhoneAccount} via the extras.
903      *
904      * @return The extras.
905      */
getExtras()906     public Bundle getExtras() {
907         return mExtras;
908     }
909 
910     /**
911      * The audio routes supported by this {@code PhoneAccount}.
912      *
913      * @hide
914      */
getSupportedAudioRoutes()915     public int getSupportedAudioRoutes() {
916         return mSupportedAudioRoutes;
917     }
918 
919     /**
920      * The icon to represent this {@code PhoneAccount}.
921      *
922      * @return The icon.
923      */
getIcon()924     public Icon getIcon() {
925         return mIcon;
926     }
927 
928     /**
929      * Indicates whether the user has enabled this {@code PhoneAccount} or not. This value is only
930      * populated for {@code PhoneAccount}s returned by {@link TelecomManager#getPhoneAccount}.
931      *
932      * @return {@code true} if the account is enabled by the user, {@code false} otherwise.
933      */
isEnabled()934     public boolean isEnabled() {
935         return mIsEnabled;
936     }
937 
938     /**
939      * A non-empty {@link String} representing the group that A {@link PhoneAccount} is in or an
940      * empty {@link String} if the {@link PhoneAccount} is not in a group. If this
941      * {@link PhoneAccount} is in a group, this new {@link PhoneAccount} will replace a registered
942      * {@link PhoneAccount} that is in the same group. When the {@link PhoneAccount} is replaced,
943      * its user defined defaults and enabled status will also pass to this new {@link PhoneAccount}.
944      * Only {@link PhoneAccount}s that share the same {@link ConnectionService} can be replaced.
945      *
946      * @return A non-empty String Id if this {@link PhoneAccount} belongs to a group.
947      * @hide
948      */
getGroupId()949     public String getGroupId() {
950         return mGroupId;
951     }
952 
953     /**
954      * Determines if the {@link PhoneAccount} supports calls to/from addresses with a specified URI
955      * scheme.
956      *
957      * @param uriScheme The URI scheme to check.
958      * @return {@code true} if the {@code PhoneAccount} supports calls to/from addresses with the
959      * specified URI scheme.
960      */
supportsUriScheme(String uriScheme)961     public boolean supportsUriScheme(String uriScheme) {
962         if (mSupportedUriSchemes == null || uriScheme == null) {
963             return false;
964         }
965 
966         for (String scheme : mSupportedUriSchemes) {
967             if (scheme != null && scheme.equals(uriScheme)) {
968                 return true;
969             }
970         }
971         return false;
972     }
973 
974     /**
975      * A highlight color to use in displaying information about this {@code PhoneAccount}.
976      *
977      * @return A hexadecimal color value.
978      */
getHighlightColor()979     public int getHighlightColor() {
980         return mHighlightColor;
981     }
982 
983     /**
984      * Sets the enabled state of the phone account.
985      * @hide
986      */
setIsEnabled(boolean isEnabled)987     public void setIsEnabled(boolean isEnabled) {
988         mIsEnabled = isEnabled;
989     }
990 
991     /**
992      * @return {@code true} if the {@link PhoneAccount} is self-managed, {@code false} otherwise.
993      * @hide
994      */
isSelfManaged()995     public boolean isSelfManaged() {
996         return (mCapabilities & CAPABILITY_SELF_MANAGED) == CAPABILITY_SELF_MANAGED;
997     }
998 
999     //
1000     // Parcelable implementation
1001     //
1002 
1003     @Override
describeContents()1004     public int describeContents() {
1005         return 0;
1006     }
1007 
1008     @Override
writeToParcel(Parcel out, int flags)1009     public void writeToParcel(Parcel out, int flags) {
1010         if (mAccountHandle == null) {
1011             out.writeInt(0);
1012         } else {
1013             out.writeInt(1);
1014             mAccountHandle.writeToParcel(out, flags);
1015         }
1016         if (mAddress == null) {
1017             out.writeInt(0);
1018         } else {
1019             out.writeInt(1);
1020             mAddress.writeToParcel(out, flags);
1021         }
1022         if (mSubscriptionAddress == null) {
1023             out.writeInt(0);
1024         } else {
1025             out.writeInt(1);
1026             mSubscriptionAddress.writeToParcel(out, flags);
1027         }
1028         out.writeInt(mCapabilities);
1029         out.writeInt(mHighlightColor);
1030         out.writeCharSequence(mLabel);
1031         out.writeCharSequence(mShortDescription);
1032         out.writeStringList(mSupportedUriSchemes);
1033 
1034         if (mIcon == null) {
1035             out.writeInt(0);
1036         } else {
1037             out.writeInt(1);
1038             mIcon.writeToParcel(out, flags);
1039         }
1040         out.writeByte((byte) (mIsEnabled ? 1 : 0));
1041         out.writeBundle(mExtras);
1042         out.writeString(mGroupId);
1043         out.writeInt(mSupportedAudioRoutes);
1044     }
1045 
1046     public static final @android.annotation.NonNull Creator<PhoneAccount> CREATOR
1047             = new Creator<PhoneAccount>() {
1048         @Override
1049         public PhoneAccount createFromParcel(Parcel in) {
1050             return new PhoneAccount(in);
1051         }
1052 
1053         @Override
1054         public PhoneAccount[] newArray(int size) {
1055             return new PhoneAccount[size];
1056         }
1057     };
1058 
PhoneAccount(Parcel in)1059     private PhoneAccount(Parcel in) {
1060         if (in.readInt() > 0) {
1061             mAccountHandle = PhoneAccountHandle.CREATOR.createFromParcel(in);
1062         } else {
1063             mAccountHandle = null;
1064         }
1065         if (in.readInt() > 0) {
1066             mAddress = Uri.CREATOR.createFromParcel(in);
1067         } else {
1068             mAddress = null;
1069         }
1070         if (in.readInt() > 0) {
1071             mSubscriptionAddress = Uri.CREATOR.createFromParcel(in);
1072         } else {
1073             mSubscriptionAddress = null;
1074         }
1075         mCapabilities = in.readInt();
1076         mHighlightColor = in.readInt();
1077         mLabel = in.readCharSequence();
1078         mShortDescription = in.readCharSequence();
1079         mSupportedUriSchemes = Collections.unmodifiableList(in.createStringArrayList());
1080         if (in.readInt() > 0) {
1081             mIcon = Icon.CREATOR.createFromParcel(in);
1082         } else {
1083             mIcon = null;
1084         }
1085         mIsEnabled = in.readByte() == 1;
1086         mExtras = in.readBundle();
1087         mGroupId = in.readString();
1088         mSupportedAudioRoutes = in.readInt();
1089     }
1090 
1091     @Override
toString()1092     public String toString() {
1093         StringBuilder sb = new StringBuilder().append("[[")
1094                 .append(mIsEnabled ? 'X' : ' ')
1095                 .append("] PhoneAccount: ")
1096                 .append(mAccountHandle)
1097                 .append(" Capabilities: ")
1098                 .append(capabilitiesToString())
1099                 .append(" Audio Routes: ")
1100                 .append(audioRoutesToString())
1101                 .append(" Schemes: ");
1102         for (String scheme : mSupportedUriSchemes) {
1103             sb.append(scheme)
1104                     .append(" ");
1105         }
1106         sb.append(" Extras: ");
1107         sb.append(mExtras);
1108         sb.append(" GroupId: ");
1109         sb.append(Log.pii(mGroupId));
1110         sb.append("]");
1111         return sb.toString();
1112     }
1113 
1114     /**
1115      * Generates a string representation of a capabilities bitmask.
1116      *
1117      * @return String representation of the capabilities bitmask.
1118      * @hide
1119      */
capabilitiesToString()1120     public String capabilitiesToString() {
1121         StringBuilder sb = new StringBuilder();
1122         if (hasCapabilities(CAPABILITY_SELF_MANAGED)) {
1123             sb.append("SelfManaged ");
1124         }
1125         if (hasCapabilities(CAPABILITY_SUPPORTS_VIDEO_CALLING)) {
1126             sb.append("SuppVideo ");
1127         }
1128         if (hasCapabilities(CAPABILITY_VIDEO_CALLING)) {
1129             sb.append("Video ");
1130         }
1131         if (hasCapabilities(CAPABILITY_VIDEO_CALLING_RELIES_ON_PRESENCE)) {
1132             sb.append("Presence ");
1133         }
1134         if (hasCapabilities(CAPABILITY_CALL_PROVIDER)) {
1135             sb.append("CallProvider ");
1136         }
1137         if (hasCapabilities(CAPABILITY_CALL_SUBJECT)) {
1138             sb.append("CallSubject ");
1139         }
1140         if (hasCapabilities(CAPABILITY_CONNECTION_MANAGER)) {
1141             sb.append("ConnectionMgr ");
1142         }
1143         if (hasCapabilities(CAPABILITY_EMERGENCY_CALLS_ONLY)) {
1144             sb.append("EmergOnly ");
1145         }
1146         if (hasCapabilities(CAPABILITY_MULTI_USER)) {
1147             sb.append("MultiUser ");
1148         }
1149         if (hasCapabilities(CAPABILITY_PLACE_EMERGENCY_CALLS)) {
1150             sb.append("PlaceEmerg ");
1151         }
1152         if (hasCapabilities(CAPABILITY_EMERGENCY_PREFERRED)) {
1153             sb.append("EmerPrefer ");
1154         }
1155         if (hasCapabilities(CAPABILITY_EMERGENCY_VIDEO_CALLING)) {
1156             sb.append("EmergVideo ");
1157         }
1158         if (hasCapabilities(CAPABILITY_SIM_SUBSCRIPTION)) {
1159             sb.append("SimSub ");
1160         }
1161         if (hasCapabilities(CAPABILITY_RTT)) {
1162             sb.append("Rtt ");
1163         }
1164         if (hasCapabilities(CAPABILITY_ADHOC_CONFERENCE_CALLING)) {
1165             sb.append("AdhocConf ");
1166         }
1167         if (hasCapabilities(CAPABILITY_CALL_COMPOSER)) {
1168             sb.append("CallComposer ");
1169         }
1170         if (hasCapabilities(CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS)) {
1171             sb.append("SuppVoice ");
1172         }
1173         if (hasCapabilities(CAPABILITY_VOICE_CALLING_AVAILABLE)) {
1174             sb.append("Voice ");
1175         }
1176         return sb.toString();
1177     }
1178 
audioRoutesToString()1179     private String audioRoutesToString() {
1180         StringBuilder sb = new StringBuilder();
1181 
1182         if (hasAudioRoutes(CallAudioState.ROUTE_BLUETOOTH)) {
1183             sb.append("B");
1184         }
1185         if (hasAudioRoutes(CallAudioState.ROUTE_EARPIECE)) {
1186             sb.append("E");
1187         }
1188         if (hasAudioRoutes(CallAudioState.ROUTE_SPEAKER)) {
1189             sb.append("S");
1190         }
1191         if (hasAudioRoutes(CallAudioState.ROUTE_WIRED_HEADSET)) {
1192             sb.append("W");
1193         }
1194 
1195         return sb.toString();
1196     }
1197 
1198     /**
1199      * Determines if two {@link Bundle}s are equal.
1200      * @param extras First {@link Bundle} to check.
1201      * @param newExtras {@link Bundle} to compare against.
1202      * @return {@code true} if the {@link Bundle}s are equal, {@code false} otherwise.
1203      */
areBundlesEqual(Bundle extras, Bundle newExtras)1204     private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) {
1205         if (extras == null || newExtras == null) {
1206             return extras == newExtras;
1207         }
1208 
1209         if (extras.size() != newExtras.size()) {
1210             return false;
1211         }
1212 
1213         for(String key : extras.keySet()) {
1214             if (key != null) {
1215                 final Object value = extras.get(key);
1216                 final Object newValue = newExtras.get(key);
1217                 if (!Objects.equals(value, newValue)) {
1218                     return false;
1219                 }
1220             }
1221         }
1222         return true;
1223     }
1224 }
1225