1 /* 2 * Copyright (c) 2016, 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 package com.android.car.stream.telecom; 17 18 import android.content.BroadcastReceiver; 19 import android.content.ComponentName; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.IntentFilter; 23 import android.content.ServiceConnection; 24 import android.os.AsyncTask; 25 import android.os.IBinder; 26 import android.os.SystemClock; 27 import android.telecom.Call; 28 import android.telecom.CallAudioState; 29 import android.telecom.TelecomManager; 30 import android.util.Log; 31 import com.android.car.stream.StreamCard; 32 import com.android.car.stream.StreamProducer; 33 import com.android.car.stream.telecom.StreamInCallService.StreamInCallServiceBinder; 34 35 /** 36 * A {@link StreamProducer} that listens for active call events and produces a {@link StreamCard} 37 */ 38 public class CurrentCallStreamProducer extends StreamProducer 39 implements StreamInCallService.InCallServiceCallback { 40 private static final String TAG = "CurrentCallProducer"; 41 42 private StreamInCallService mInCallService; 43 private PhoneCallback mPhoneCallback; 44 private CurrentCallActionReceiver mCallActionReceiver; 45 private Call mCurrentCall; 46 private long mCurrentCallStartTime; 47 48 private CurrentCallConverter mConverter; 49 private AsyncTask mUpdateStreamItemTask; 50 51 private String mDialerPackage; 52 private TelecomManager mTelecomManager; 53 CurrentCallStreamProducer(Context context)54 public CurrentCallStreamProducer(Context context) { 55 super(context); 56 } 57 58 @Override start()59 public void start() { 60 super.start(); 61 if (Log.isLoggable(TAG, Log.DEBUG)) { 62 Log.d(TAG, "current call producer started"); 63 } 64 mTelecomManager = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE); 65 mDialerPackage = mTelecomManager.getDefaultDialerPackage(); 66 mConverter = new CurrentCallConverter(mContext); 67 mPhoneCallback = new PhoneCallback(); 68 69 Intent inCallServiceIntent = new Intent(mContext, StreamInCallService.class); 70 inCallServiceIntent.setAction(StreamInCallService.LOCAL_INCALL_SERVICE_BIND_ACTION); 71 mContext.bindService(inCallServiceIntent, mServiceConnection, Context.BIND_AUTO_CREATE); 72 } 73 74 @Override stop()75 public void stop() { 76 mContext.unbindService(mServiceConnection); 77 super.stop(); 78 } 79 acceptCall()80 private void acceptCall() { 81 synchronized (mTelecomManager) { 82 if (mCurrentCall != null && mCurrentCall.getState() == Call.STATE_RINGING) { 83 mCurrentCall.answer(0 /* videoState */); 84 } 85 } 86 } 87 disconnectCall()88 private void disconnectCall() { 89 synchronized (mTelecomManager) { 90 if (mCurrentCall != null) { 91 mCurrentCall.disconnect(); 92 } 93 } 94 } 95 96 @Override onCallAdded(Call call)97 public void onCallAdded(Call call) { 98 if (Log.isLoggable(TAG, Log.DEBUG)) { 99 Log.d(TAG, "on call added, state: " + call.getState()); 100 } 101 mCurrentCall = call; 102 updateStreamCard(mCurrentCall, mContext); 103 call.registerCallback(mPhoneCallback); 104 } 105 106 @Override onCallRemoved(Call call)107 public void onCallRemoved(Call call) { 108 if (Log.isLoggable(TAG, Log.DEBUG)) { 109 Log.d(TAG, "on call removed, state: " + call.getState()); 110 } 111 call.unregisterCallback(mPhoneCallback); 112 updateStreamCard(call, mContext); 113 mCurrentCall = null; 114 } 115 116 @Override onCallAudioStateChanged(CallAudioState audioState)117 public void onCallAudioStateChanged(CallAudioState audioState) { 118 if (mCurrentCall != null && audioState != null) { 119 if (Log.isLoggable(TAG, Log.DEBUG)) { 120 Log.d(TAG, "audio state changed, is muted? " + audioState.isMuted()); 121 } 122 updateStreamCard(mCurrentCall, mContext); 123 } 124 } 125 clearUpdateStreamItemTask()126 private void clearUpdateStreamItemTask() { 127 if (mUpdateStreamItemTask != null) { 128 mUpdateStreamItemTask.cancel(false); 129 mUpdateStreamItemTask = null; 130 } 131 } 132 updateStreamCard(final Call call, final Context context)133 private void updateStreamCard(final Call call, final Context context) { 134 // Only one update may be active at a time. 135 clearUpdateStreamItemTask(); 136 137 mUpdateStreamItemTask = new AsyncTask<Void, Void, StreamCard>() { 138 @Override 139 protected StreamCard doInBackground(Void... voids) { 140 try { 141 return mConverter.convert(call, context, mInCallService.isMuted(), 142 mCurrentCallStartTime, mDialerPackage); 143 } catch (Exception e) { 144 Log.e(TAG, "Failed to create StreamItem.", e); 145 throw e; 146 } 147 } 148 149 @Override 150 protected void onPostExecute(StreamCard card) { 151 if (call.getState() == Call.STATE_DISCONNECTED) { 152 removeCard(card); 153 } else { 154 postCard(card); 155 } 156 } 157 }.execute(); 158 } 159 160 private class PhoneCallback extends Call.Callback { 161 @Override onStateChanged(Call call, int state)162 public void onStateChanged(Call call, int state) { 163 if (Log.isLoggable(TAG, Log.DEBUG)) { 164 Log.d(TAG, "onStateChanged call: " + call + ", state: " + state); 165 } 166 167 if (state == Call.STATE_ACTIVE) { 168 mCurrentCallStartTime = SystemClock.elapsedRealtime(); 169 } else { 170 mCurrentCallStartTime = 0; 171 } 172 173 switch (state) { 174 // TODO: Determine if a HUD or stream card should be displayed. 175 case Call.STATE_RINGING: // Incoming call is ringing. 176 case Call.STATE_DIALING: // Outgoing call that is dialing. 177 case Call.STATE_ACTIVE: // Call is connected 178 case Call.STATE_DISCONNECTING: // Call is being disconnected 179 case Call.STATE_DISCONNECTED: // Call has finished. 180 updateStreamCard(call, mContext); 181 mCurrentCall = call; 182 break; 183 default: 184 } 185 } 186 } 187 188 private class CurrentCallActionReceiver extends BroadcastReceiver { 189 @Override onReceive(Context context, Intent intent)190 public void onReceive(Context context, Intent intent) { 191 String intentAction = intent.getAction(); 192 if (!TelecomConstants.INTENT_ACTION_STREAM_CALL_CONTROL.equals(intentAction)) { 193 return; 194 } 195 196 String action = intent.getStringExtra(TelecomConstants.EXTRA_STREAM_CALL_ACTION); 197 switch (action) { 198 case TelecomConstants.ACTION_MUTE: 199 mInCallService.setMuted(true); 200 break; 201 case TelecomConstants.ACTION_UNMUTE: 202 mInCallService.setMuted(false); 203 break; 204 case TelecomConstants.ACTION_ACCEPT_CALL: 205 acceptCall(); 206 break; 207 case TelecomConstants.ACTION_HANG_UP_CALL: 208 disconnectCall(); 209 break; 210 default: 211 } 212 } 213 } 214 215 private ServiceConnection mServiceConnection = new ServiceConnection() { 216 @Override 217 public void onServiceConnected(ComponentName name, IBinder service) { 218 StreamInCallServiceBinder binder = (StreamInCallServiceBinder) service; 219 mInCallService = binder.getService(); 220 mInCallService.setCallback(CurrentCallStreamProducer.this); 221 222 if (mCallActionReceiver == null) { 223 mCallActionReceiver = new CurrentCallActionReceiver(); 224 mContext.registerReceiver(mCallActionReceiver, 225 new IntentFilter(TelecomConstants.INTENT_ACTION_STREAM_CALL_CONTROL)); 226 } 227 } 228 229 @Override 230 public void onServiceDisconnected(ComponentName name) { 231 mInCallService = null; 232 } 233 }; 234 } 235