1 /*
2  * Copyright (C) 2019 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.car.dialer.telecom;
18 
19 import android.content.Context;
20 import android.content.Intent;
21 import android.telecom.Call;
22 
23 import androidx.annotation.MainThread;
24 
25 import com.android.car.dialer.Constants;
26 import com.android.car.dialer.R;
27 import com.android.car.dialer.log.L;
28 import com.android.car.dialer.notification.InCallNotificationController;
29 import com.android.car.dialer.ui.activecall.InCallActivity;
30 import com.android.car.dialer.ui.activecall.InCallViewModel;
31 
32 import java.util.ArrayList;
33 
34 /**
35  * Routes a call to different path depending on its state. If there is any {@link
36  * InCallServiceImpl.ActiveCallListChangedCallback} that already handles the call, i.e. the {@link
37  * InCallViewModel} that actively updates the in call page, then we don't show HUN for the ringing
38  * call or attempt to start the in call page again.
39  */
40 class InCallRouter {
41 
42     private static final String TAG = "CD.InCallRouter";
43 
44     private final Context mContext;
45     private final InCallNotificationController mInCallNotificationController;
46     private final ArrayList<InCallServiceImpl.ActiveCallListChangedCallback>
47             mActiveCallListChangedCallbacks = new ArrayList<>();
48     private final ProjectionCallHandler mProjectionCallHandler;
49 
50     private final boolean mShowFullscreenIncallUi;
51 
InCallRouter(Context context)52     InCallRouter(Context context) {
53         mContext = context;
54         mShowFullscreenIncallUi = context.getResources().getBoolean(
55                 R.bool.config_show_fullscreen_incall_ui);
56         mInCallNotificationController = InCallNotificationController.get();
57         mProjectionCallHandler = new ProjectionCallHandler(context);
58     }
59 
start()60     void start() {
61         mProjectionCallHandler.start();
62         mActiveCallListChangedCallbacks.add(mProjectionCallHandler);
63     }
64 
stop()65     void stop() {
66         mActiveCallListChangedCallbacks.remove(mProjectionCallHandler);
67         mProjectionCallHandler.stop();
68     }
69 
70     /**
71      * Routes the added call to the correct path:
72      * <ul>
73      * <li> First dispatches it to the {@link InCallServiceImpl.ActiveCallListChangedCallback}s.
74      * <li> If the ringing call is not handled by callbacks, it will show a HUN.
75      * <li> If the call is in other state and not handled by callbacks, it will try to launch the in
76      * call page.
77      */
onCallAdded(Call call)78     void onCallAdded(Call call) {
79         boolean isHandled = routeToActiveCallListChangedCallback(call);
80         if (isHandled) {
81             return;
82         }
83 
84         int state = call.getState();
85         if (state == Call.STATE_RINGING) {
86             routeToNotification(call);
87         } else if (state != Call.STATE_DISCONNECTED) {
88             // Don't launch the in call page if state is disconnected.
89             // Otherwise, the InCallActivity finishes right after onCreate() and flashes.
90             routeToInCallPage(false);
91         }
92     }
93 
94     /**
95      * Called by {@link InCallServiceImpl#onCallRemoved(Call)}. It notifies the {@link
96      * InCallServiceImpl.ActiveCallListChangedCallback}s to update the active call list.
97      */
onCallRemoved(Call call)98     void onCallRemoved(Call call) {
99         for (InCallServiceImpl.ActiveCallListChangedCallback callback :
100                 mActiveCallListChangedCallbacks) {
101             callback.onTelecomCallRemoved(call);
102         }
103     }
104 
105     @MainThread
registerActiveCallListChangedCallback( InCallServiceImpl.ActiveCallListChangedCallback callback)106     void registerActiveCallListChangedCallback(
107             InCallServiceImpl.ActiveCallListChangedCallback callback) {
108         mActiveCallListChangedCallbacks.add(callback);
109     }
110 
111     @MainThread
unregisterActiveCallHandler(InCallServiceImpl.ActiveCallListChangedCallback callback)112     void unregisterActiveCallHandler(InCallServiceImpl.ActiveCallListChangedCallback callback) {
113         mActiveCallListChangedCallbacks.remove(callback);
114     }
115 
116     /**
117      * Dispatches the call to {@link InCallServiceImpl.ActiveCallListChangedCallback}.
118      */
routeToActiveCallListChangedCallback(Call call)119     private boolean routeToActiveCallListChangedCallback(Call call) {
120         boolean isHandled = false;
121         for (InCallServiceImpl.ActiveCallListChangedCallback callback :
122                 mActiveCallListChangedCallbacks) {
123             if (callback.onTelecomCallAdded(call)) {
124                 isHandled = true;
125             }
126         }
127 
128         return isHandled;
129     }
130 
131     /**
132      * Presents the ringing call in HUN.
133      */
routeToNotification(Call call)134     private void routeToNotification(Call call) {
135         mInCallNotificationController.showInCallNotification(call);
136         call.registerCallback(new Call.Callback() {
137             @Override
138             public void onStateChanged(Call call, int state) {
139                 L.d(TAG, "Ringing call state changed to %d", state);
140                 if (call.getState() != Call.STATE_DISCONNECTED) {
141                     // Don't launch the in call page if state is disconnected. Otherwise, the
142                     // InCallActivity finishes right after onCreate() and flashes.
143                     routeToInCallPage(false);
144                 }
145                 mInCallNotificationController.cancelInCallNotification(call);
146                 call.unregisterCallback(this);
147             }
148         });
149     }
150 
151     /**
152      * Launches {@link InCallActivity} and presents the on going call in the in call page.
153      */
routeToInCallPage(boolean showDialpad)154     void routeToInCallPage(boolean showDialpad) {
155         // It has been configured not to show the fullscreen incall ui.
156         if (!mShowFullscreenIncallUi) {
157             return;
158         }
159 
160         Intent launchIntent = new Intent(mContext, InCallActivity.class);
161         launchIntent.putExtra(Constants.Intents.EXTRA_SHOW_INCOMING_CALL, showDialpad);
162         mContext.startActivity(launchIntent);
163     }
164 }
165