1 /*
2  * Copyright (C) 2017 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.incallui.call;
18 
19 import android.os.Handler;
20 import android.os.Looper;
21 import android.support.annotation.NonNull;
22 import android.telecom.Call;
23 import android.util.ArraySet;
24 import com.android.contacts.common.compat.CallCompat;
25 import com.android.dialer.common.Assert;
26 import com.android.dialer.common.LogUtil;
27 import java.util.Collections;
28 import java.util.Set;
29 import java.util.concurrent.ConcurrentHashMap;
30 
31 /**
32  * Tracks the external calls known to the InCall UI.
33  *
34  * <p>External calls are those with {@code android.telecom.Call.Details#PROPERTY_IS_EXTERNAL_CALL}.
35  */
36 public class ExternalCallList {
37 
38   private final Set<Call> externalCalls = new ArraySet<>();
39   private final Set<ExternalCallListener> externalCallListeners =
40       Collections.newSetFromMap(new ConcurrentHashMap<ExternalCallListener, Boolean>(8, 0.9f, 1));
41   /** Handles {@link android.telecom.Call.Callback} callbacks. */
42   private final Call.Callback telecomCallCallback =
43       new Call.Callback() {
44         @Override
45         public void onDetailsChanged(Call call, Call.Details details) {
46           notifyExternalCallUpdated(call);
47         }
48       };
49 
50   /** Begins tracking an external call and notifies listeners of the new call. */
onCallAdded(Call telecomCall)51   public void onCallAdded(Call telecomCall) {
52     Assert.checkArgument(
53         telecomCall.getDetails().hasProperty(CallCompat.Details.PROPERTY_IS_EXTERNAL_CALL));
54     externalCalls.add(telecomCall);
55     telecomCall.registerCallback(telecomCallCallback, new Handler(Looper.getMainLooper()));
56     notifyExternalCallAdded(telecomCall);
57   }
58 
59   /** Stops tracking an external call and notifies listeners of the removal of the call. */
onCallRemoved(Call telecomCall)60   public void onCallRemoved(Call telecomCall) {
61     if (!externalCalls.contains(telecomCall)) {
62       // This can happen on M for external calls from blocked numbers
63       LogUtil.i("ExternalCallList.onCallRemoved", "attempted to remove unregistered call");
64       return;
65     }
66     externalCalls.remove(telecomCall);
67     telecomCall.unregisterCallback(telecomCallCallback);
68     notifyExternalCallRemoved(telecomCall);
69   }
70 
71   /** Adds a new listener to external call events. */
addExternalCallListener(@onNull ExternalCallListener listener)72   public void addExternalCallListener(@NonNull ExternalCallListener listener) {
73     externalCallListeners.add(listener);
74   }
75 
76   /** Removes a listener to external call events. */
removeExternalCallListener(@onNull ExternalCallListener listener)77   public void removeExternalCallListener(@NonNull ExternalCallListener listener) {
78     if (!externalCallListeners.contains(listener)) {
79       LogUtil.i(
80           "ExternalCallList.removeExternalCallListener",
81           "attempt to remove unregistered listener.");
82     }
83     externalCallListeners.remove(listener);
84   }
85 
isCallTracked(@onNull android.telecom.Call telecomCall)86   public boolean isCallTracked(@NonNull android.telecom.Call telecomCall) {
87     return externalCalls.contains(telecomCall);
88   }
89 
90   /** Notifies listeners of the addition of a new external call. */
notifyExternalCallAdded(Call call)91   private void notifyExternalCallAdded(Call call) {
92     for (ExternalCallListener listener : externalCallListeners) {
93       listener.onExternalCallAdded(call);
94     }
95   }
96 
97   /** Notifies listeners of the removal of an external call. */
notifyExternalCallRemoved(Call call)98   private void notifyExternalCallRemoved(Call call) {
99     for (ExternalCallListener listener : externalCallListeners) {
100       listener.onExternalCallRemoved(call);
101     }
102   }
103 
104   /** Notifies listeners of changes to an external call. */
notifyExternalCallUpdated(Call call)105   private void notifyExternalCallUpdated(Call call) {
106     if (!call.getDetails().hasProperty(CallCompat.Details.PROPERTY_IS_EXTERNAL_CALL)) {
107       // A previous external call has been pulled and is now a regular call, so we will remove
108       // it from the external call listener and ensure that the CallList is informed of the
109       // change.
110       onCallRemoved(call);
111 
112       for (ExternalCallListener listener : externalCallListeners) {
113         listener.onExternalCallPulled(call);
114       }
115     } else {
116       for (ExternalCallListener listener : externalCallListeners) {
117         listener.onExternalCallUpdated(call);
118       }
119     }
120   }
121 
122   /**
123    * Defines events which the {@link ExternalCallList} exposes to interested components (e.g. {@link
124    * com.android.incallui.ExternalCallNotifier ExternalCallNotifier}).
125    */
126   public interface ExternalCallListener {
127 
onExternalCallAdded(Call call)128     void onExternalCallAdded(Call call);
129 
onExternalCallRemoved(Call call)130     void onExternalCallRemoved(Call call);
131 
onExternalCallUpdated(Call call)132     void onExternalCallUpdated(Call call);
133 
onExternalCallPulled(Call call)134     void onExternalCallPulled(Call call);
135   }
136 }
137