blob: 509307b7c3356b7dbeeaa56a81199f82f280d9f8 [file] [log] [blame]
Santos Cordonb64c1502014-05-21 21:21:49 -07001/*
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
17package com.android.telecomm;
18
Santos Cordon46592f02014-07-07 15:11:35 -070019import android.app.AppOpsManager;
Yorke Leeceb96c92014-06-11 16:34:44 -070020import android.content.ComponentName;
Ihab Awad98a55602014-06-30 21:27:28 -070021import android.content.Context;
Yorke Leeceb96c92014-06-11 16:34:44 -070022import android.content.res.Resources;
Ihab Awad60509462014-06-14 16:43:08 -070023import android.net.Uri;
Santos Cordon46592f02014-07-07 15:11:35 -070024import android.os.Binder;
Santos Cordonb64c1502014-05-21 21:21:49 -070025import android.os.Handler;
Santos Cordon23baed32014-06-27 14:45:39 -070026import android.os.Looper;
Santos Cordonb64c1502014-05-21 21:21:49 -070027import android.os.Message;
28import android.os.ServiceManager;
Santos Cordon46592f02014-07-07 15:11:35 -070029import android.phone.PhoneManager;
Santos Cordon23baed32014-06-27 14:45:39 -070030import android.telecomm.CallState;
Ihab Awad98a55602014-06-30 21:27:28 -070031import android.telecomm.PhoneAccount;
Ihab Awad502e5fe2014-07-09 12:34:48 -070032import android.telecomm.PhoneAccountMetadata;
Santos Cordon46592f02014-07-07 15:11:35 -070033import android.telecomm.TelecommManager;
34import android.telephony.TelephonyManager;
35
36import com.android.internal.telecomm.ITelecommService;
Santos Cordonb64c1502014-05-21 21:21:49 -070037
Ihab Awad502e5fe2014-07-09 12:34:48 -070038import java.util.ArrayList;
39import java.util.HashMap;
Ihab Awad60509462014-06-14 16:43:08 -070040import java.util.List;
Ihab Awad502e5fe2014-07-09 12:34:48 -070041import java.util.Map;
Santos Cordonb64c1502014-05-21 21:21:49 -070042
43/**
44 * Implementation of the ITelecomm interface.
45 */
46public class TelecommServiceImpl extends ITelecommService.Stub {
Santos Cordon23baed32014-06-27 14:45:39 -070047 /**
48 * A request object for use with {@link MainThreadHandler}. Requesters should wait() on the
49 * request after sending. The main thread will notify the request when it is complete.
50 */
51 private static final class MainThreadRequest {
52 /** The result of the request that is run on the main thread */
53 public Object result;
54 }
Santos Cordonb64c1502014-05-21 21:21:49 -070055
56 /**
57 * A handler that processes messages on the main thread in the phone process. Since many
58 * of the Phone calls are not thread safe this is needed to shuttle the requests from the
59 * inbound binder threads to the main thread in the phone process.
60 */
Santos Cordon23baed32014-06-27 14:45:39 -070061 private final class MainThreadHandler extends Handler {
Santos Cordonb64c1502014-05-21 21:21:49 -070062 @Override
63 public void handleMessage(Message msg) {
Santos Cordon23baed32014-06-27 14:45:39 -070064 if (msg.obj instanceof MainThreadRequest) {
65 MainThreadRequest request = (MainThreadRequest) msg.obj;
66 Object result = null;
67 switch (msg.what) {
68 case MSG_SILENCE_RINGER:
69 mCallsManager.getRinger().silence();
70 break;
71 case MSG_SHOW_CALL_SCREEN:
72 mCallsManager.getInCallController().bringToForeground(msg.arg1 == 1);
73 break;
Santos Cordon23baed32014-06-27 14:45:39 -070074 case MSG_END_CALL:
75 result = endCallInternal();
76 break;
77 case MSG_ACCEPT_RINGING_CALL:
78 acceptRingingCallInternal();
79 break;
Santos Cordon64c7e962014-07-02 15:15:27 -070080 case MSG_CANCEL_MISSED_CALLS_NOTIFICATION:
81 mMissedCallNotifier.clearMissedCalls();
82 break;
Sailesh Nepalb88795a2014-07-15 14:53:27 -070083 case MSG_IS_TTY_SUPPORTED:
84 result = mCallsManager.isTtySupported();
85 break;
86 case MSG_GET_CURRENT_TTY_MODE:
87 result = mCallsManager.getCurrentTtyMode();
88 break;
Santos Cordon23baed32014-06-27 14:45:39 -070089 }
90
91 if (result != null) {
92 request.result = result;
93 synchronized(request) {
94 request.notifyAll();
95 }
96 }
Santos Cordonb64c1502014-05-21 21:21:49 -070097 }
98 }
Santos Cordon23baed32014-06-27 14:45:39 -070099 }
Santos Cordonb64c1502014-05-21 21:21:49 -0700100
Santos Cordon7da72ef2014-06-25 15:50:22 -0700101 /** Private constructor; @see init() */
Santos Cordon23baed32014-06-27 14:45:39 -0700102 private static final String TAG = TelecommServiceImpl.class.getSimpleName();
103
104 private static final String SERVICE_NAME = "telecomm";
105
106 private static final int MSG_SILENCE_RINGER = 1;
107 private static final int MSG_SHOW_CALL_SCREEN = 2;
Santos Cordon626697a2014-07-10 22:36:37 -0700108 private static final int MSG_END_CALL = 3;
109 private static final int MSG_ACCEPT_RINGING_CALL = 4;
110 private static final int MSG_CANCEL_MISSED_CALLS_NOTIFICATION = 5;
Sailesh Nepalb88795a2014-07-15 14:53:27 -0700111 private static final int MSG_IS_TTY_SUPPORTED = 6;
112 private static final int MSG_GET_CURRENT_TTY_MODE = 7;
Santos Cordon23baed32014-06-27 14:45:39 -0700113
114 /** The singleton instance. */
115 private static TelecommServiceImpl sInstance;
116
Santos Cordon46592f02014-07-07 15:11:35 -0700117 private final MainThreadHandler mMainThreadHandler = new MainThreadHandler();
Santos Cordon23baed32014-06-27 14:45:39 -0700118 private final CallsManager mCallsManager = CallsManager.getInstance();
Santos Cordon64c7e962014-07-02 15:15:27 -0700119 private final MissedCallNotifier mMissedCallNotifier;
Santos Cordon46592f02014-07-07 15:11:35 -0700120 private final AppOpsManager mAppOpsManager;
Santos Cordon23baed32014-06-27 14:45:39 -0700121
Santos Cordon64c7e962014-07-02 15:15:27 -0700122 private TelecommServiceImpl(MissedCallNotifier missedCallNotifier) {
123 mMissedCallNotifier = missedCallNotifier;
Santos Cordon46592f02014-07-07 15:11:35 -0700124 mAppOpsManager =
125 (AppOpsManager) TelecommApp.getInstance().getSystemService(Context.APP_OPS_SERVICE);
126
Santos Cordon7da72ef2014-06-25 15:50:22 -0700127 publish();
128 }
129
Santos Cordonb64c1502014-05-21 21:21:49 -0700130 /**
131 * Initialize the singleton TelecommServiceImpl instance.
132 * This is only done once, at startup, from TelecommApp.onCreate().
133 */
Santos Cordon64c7e962014-07-02 15:15:27 -0700134 static TelecommServiceImpl init(MissedCallNotifier missedCallNotifier) {
Santos Cordonb64c1502014-05-21 21:21:49 -0700135 synchronized (TelecommServiceImpl.class) {
136 if (sInstance == null) {
Santos Cordon64c7e962014-07-02 15:15:27 -0700137 sInstance = new TelecommServiceImpl(missedCallNotifier);
Santos Cordonb64c1502014-05-21 21:21:49 -0700138 } else {
139 Log.wtf(TAG, "init() called multiple times! sInstance %s", sInstance);
140 }
141 return sInstance;
142 }
143 }
144
Santos Cordonb64c1502014-05-21 21:21:49 -0700145 //
Santos Cordon23baed32014-06-27 14:45:39 -0700146 // Implementation of the ITelecommService interface.
Santos Cordonb64c1502014-05-21 21:21:49 -0700147 //
148
Ihab Awad502e5fe2014-07-09 12:34:48 -0700149 private static Map<PhoneAccount, PhoneAccountMetadata> sMetadataByAccount = new HashMap<>();
150
151 static {
Ihab Awad98a55602014-06-30 21:27:28 -0700152 // TODO (STOPSHIP): Static list of Accounts for testing and UX work only.
153 ComponentName componentName = new ComponentName(
154 "com.android.telecomm",
155 TelecommServiceImpl.class.getName()); // This field is a no-op
156 Context app = TelecommApp.getInstance();
157
Ihab Awad502e5fe2014-07-09 12:34:48 -0700158 PhoneAccount[] accounts = new PhoneAccount[] {
Ihab Awad98a55602014-06-30 21:27:28 -0700159 new PhoneAccount(
160 componentName,
161 "account0",
162 Uri.parse("tel:999-555-1212"),
Ihab Awad502e5fe2014-07-09 12:34:48 -0700163 0),
Ihab Awad98a55602014-06-30 21:27:28 -0700164 new PhoneAccount(
165 componentName,
166 "account1",
167 Uri.parse("tel:333-111-2222"),
Ihab Awad502e5fe2014-07-09 12:34:48 -0700168 0),
Ihab Awad98a55602014-06-30 21:27:28 -0700169 new PhoneAccount(
170 componentName,
171 "account2",
172 Uri.parse("mailto:two@example.com"),
Ihab Awad502e5fe2014-07-09 12:34:48 -0700173 0),
Ihab Awad98a55602014-06-30 21:27:28 -0700174 new PhoneAccount(
175 componentName,
176 "account3",
177 Uri.parse("mailto:three@example.com"),
Ihab Awad502e5fe2014-07-09 12:34:48 -0700178 0)
179 };
180
181 sMetadataByAccount.put(
182 accounts[0],
183 new PhoneAccountMetadata(
184 accounts[0],
185 0,
186 app.getString(R.string.test_account_0_label),
187 app.getString(R.string.test_account_0_short_description)));
188 sMetadataByAccount.put(
189 accounts[1],
190 new PhoneAccountMetadata(
191 accounts[1],
192 0,
193 app.getString(R.string.test_account_1_label),
194 app.getString(R.string.test_account_1_short_description)));
195 sMetadataByAccount.put(
196 accounts[2],
197 new PhoneAccountMetadata(
198 accounts[2],
199 0,
200 app.getString(R.string.test_account_2_label),
201 app.getString(R.string.test_account_2_short_description)));
202 sMetadataByAccount.put(
203 accounts[3],
204 new PhoneAccountMetadata(
205 accounts[3],
206 0,
Ihab Awad98a55602014-06-30 21:27:28 -0700207 app.getString(R.string.test_account_3_label),
Ihab Awad502e5fe2014-07-09 12:34:48 -0700208 app.getString(R.string.test_account_3_short_description)));
Santos Cordon7da72ef2014-06-25 15:50:22 -0700209 }
210
211 @Override
Ihab Awad502e5fe2014-07-09 12:34:48 -0700212 public List<PhoneAccount> getEnabledPhoneAccounts() {
213 return new ArrayList<>(sMetadataByAccount.keySet());
Santos Cordon7da72ef2014-06-25 15:50:22 -0700214 }
215
216 @Override
Ihab Awad502e5fe2014-07-09 12:34:48 -0700217 public PhoneAccountMetadata getPhoneAccountMetadata(PhoneAccount account) {
218 return sMetadataByAccount.get(account);
219 }
220
221 @Override
222 public void registerPhoneAccount(PhoneAccount account, PhoneAccountMetadata metadata) {
223 enforceModifyPermissionOrCallingPackage(account.getComponentName().getPackageName());
224 // TODO(santoscordon) -- IMPLEMENT ...
225 }
226
227 @Override
228 public void unregisterPhoneAccount(PhoneAccount account) {
229 enforceModifyPermissionOrCallingPackage(account.getComponentName().getPackageName());
230 // TODO(santoscordon) -- IMPLEMENT ...
231 }
232
233 @Override
234 public void clearAccounts(String packageName) {
235 enforceModifyPermissionOrCallingPackage(packageName);
236 // TODO(santoscordon) -- IMPLEMENT ...
Santos Cordon7da72ef2014-06-25 15:50:22 -0700237 }
238
Santos Cordon23baed32014-06-27 14:45:39 -0700239 /**
240 * @see TelecommManager#silenceringer
241 */
Santos Cordon7da72ef2014-06-25 15:50:22 -0700242 @Override
Santos Cordonb64c1502014-05-21 21:21:49 -0700243 public void silenceRinger() {
244 Log.d(this, "silenceRinger");
Santos Cordonb64c1502014-05-21 21:21:49 -0700245 enforceModifyPermission();
Santos Cordon23baed32014-06-27 14:45:39 -0700246 sendRequestAsync(MSG_SILENCE_RINGER, 0);
Santos Cordonb64c1502014-05-21 21:21:49 -0700247 }
248
Santos Cordon23baed32014-06-27 14:45:39 -0700249 /**
250 * @see TelecommManager#getDefaultPhoneApp
251 */
Santos Cordon7da72ef2014-06-25 15:50:22 -0700252 @Override
253 public ComponentName getDefaultPhoneApp() {
254 Resources resources = TelecommApp.getInstance().getResources();
255 return new ComponentName(
256 resources.getString(R.string.ui_default_package),
257 resources.getString(R.string.dialer_default_class));
258 }
259
Santos Cordon23baed32014-06-27 14:45:39 -0700260 /**
261 * @see TelecommManager#isInAPhoneCall
262 */
263 @Override
264 public boolean isInAPhoneCall() {
265 enforceReadPermission();
Santos Cordon626697a2014-07-10 22:36:37 -0700266 // Do not use sendRequest() with this method since it could cause a deadlock with
267 // audio service, which we call into from the main thread: AudioManager.setMode().
268 return mCallsManager.hasAnyCalls();
Santos Cordon23baed32014-06-27 14:45:39 -0700269 }
270
271 /**
272 * @see TelecommManager#isRinging
273 */
274 @Override
275 public boolean isRinging() {
276 enforceReadPermission();
Santos Cordon626697a2014-07-10 22:36:37 -0700277 return mCallsManager.hasRingingCall();
Santos Cordon23baed32014-06-27 14:45:39 -0700278 }
279
280 /**
281 * @see TelecommManager#endCall
282 */
283 @Override
284 public boolean endCall() {
285 enforceModifyPermission();
286 return (boolean) sendRequest(MSG_END_CALL);
287 }
288
289 /**
290 * @see TelecommManager#acceptRingingCall
291 */
292 @Override
293 public void acceptRingingCall() {
294 enforceModifyPermission();
295 sendRequestAsync(MSG_ACCEPT_RINGING_CALL, 0);
296 }
297
Santos Cordon46592f02014-07-07 15:11:35 -0700298 /**
299 * @see PhoneManager#showCallScreen
300 */
Santos Cordon23baed32014-06-27 14:45:39 -0700301 @Override
302 public void showCallScreen(boolean showDialpad) {
Santos Cordon46592f02014-07-07 15:11:35 -0700303 enforceReadPermissionOrDefaultDialer();
Santos Cordon64c7e962014-07-02 15:15:27 -0700304 sendRequestAsync(MSG_SHOW_CALL_SCREEN, showDialpad ? 1 : 0);
305 }
306
Santos Cordon46592f02014-07-07 15:11:35 -0700307 /**
308 * @see PhoneManager#cancelMissedCallsNotification
309 */
Santos Cordon64c7e962014-07-02 15:15:27 -0700310 @Override
311 public void cancelMissedCallsNotification() {
Santos Cordon46592f02014-07-07 15:11:35 -0700312 enforceModifyPermissionOrDefaultDialer();
Santos Cordon64c7e962014-07-02 15:15:27 -0700313 sendRequestAsync(MSG_CANCEL_MISSED_CALLS_NOTIFICATION, 0);
Santos Cordon23baed32014-06-27 14:45:39 -0700314 }
315
Santos Cordon46592f02014-07-07 15:11:35 -0700316 /**
317 * @see PhoneManager#handlePinMmi
318 */
319 @Override
320 public boolean handlePinMmi(String dialString) {
321 enforceModifyPermissionOrDefaultDialer();
322
323 // Switch identity so that TelephonyManager checks Telecomm's permissions instead.
324 long token = Binder.clearCallingIdentity();
325 boolean retval = getTelephonyManager().handlePinMmi(dialString);
326 Binder.restoreCallingIdentity(token);
327
328 return retval;
329 }
330
Sailesh Nepalb88795a2014-07-15 14:53:27 -0700331 /**
332 * @see TelecommManager#isTtySupported
333 */
334 @Override
335 public boolean isTtySupported() {
336 enforceReadPermission();
337 return (boolean) sendRequest(MSG_IS_TTY_SUPPORTED);
338 }
339
340 /**
341 * @see TelecommManager#getCurrentTtyMode
342 */
343 @Override
344 public int getCurrentTtyMode() {
345 enforceReadPermission();
346 return (int) sendRequest(MSG_GET_CURRENT_TTY_MODE);
347 }
348
Santos Cordon7da72ef2014-06-25 15:50:22 -0700349 //
Santos Cordon46592f02014-07-07 15:11:35 -0700350 // Supporting methods for the ITelecommService interface implementation.
Santos Cordon7da72ef2014-06-25 15:50:22 -0700351 //
352
Santos Cordon23baed32014-06-27 14:45:39 -0700353 private void acceptRingingCallInternal() {
354 Call call = mCallsManager.getFirstCallWithState(CallState.RINGING);
355 if (call != null) {
356 call.answer();
357 }
358 }
359
360 private boolean endCallInternal() {
361 // Always operate on the foreground call if one exists, otherwise get the first call in
362 // priority order by call-state.
363 Call call = mCallsManager.getForegroundCall();
364 if (call == null) {
365 call = mCallsManager.getFirstCallWithState(
366 CallState.ACTIVE,
367 CallState.DIALING,
368 CallState.RINGING,
369 CallState.ON_HOLD);
370 }
371
372 if (call != null) {
373 if (call.getState() == CallState.RINGING) {
374 call.reject(false /* rejectWithMessage */, null);
375 } else {
376 call.disconnect();
377 }
378 return true;
379 }
380
381 return false;
Santos Cordonb64c1502014-05-21 21:21:49 -0700382 }
383
384 /**
385 * Make sure the caller has the MODIFY_PHONE_STATE permission.
386 *
387 * @throws SecurityException if the caller does not have the required permission
388 */
389 private void enforceModifyPermission() {
390 TelecommApp.getInstance().enforceCallingOrSelfPermission(
391 android.Manifest.permission.MODIFY_PHONE_STATE, null);
392 }
Santos Cordonf3671a62014-05-29 21:51:53 -0700393
Santos Cordon46592f02014-07-07 15:11:35 -0700394 private void enforceModifyPermissionOrDefaultDialer() {
395 if (!isDefaultDialerCalling()) {
396 enforceModifyPermission();
397 }
398 }
399
Ihab Awad502e5fe2014-07-09 12:34:48 -0700400 private void enforceModifyPermissionOrCallingPackage(String packageName) {
401 // TODO(santoscordon): Use a new telecomm permission for this instead of reusing modify.
402 try {
403 enforceModifyPermission();
404 } catch (SecurityException e) {
405 enforceCallingPackage(packageName);
406 }
407 }
408
Santos Cordon23baed32014-06-27 14:45:39 -0700409 private void enforceReadPermission() {
410 TelecommApp.getInstance().enforceCallingOrSelfPermission(
411 android.Manifest.permission.READ_PHONE_STATE, null);
Santos Cordonf3671a62014-05-29 21:51:53 -0700412 }
Yorke Leeceb96c92014-06-11 16:34:44 -0700413
Santos Cordon46592f02014-07-07 15:11:35 -0700414 private void enforceReadPermissionOrDefaultDialer() {
415 if (!isDefaultDialerCalling()) {
416 enforceReadPermission();
417 }
418 }
419
Ihab Awad502e5fe2014-07-09 12:34:48 -0700420 private void enforceCallingPackage(String packageName) {
421 mAppOpsManager.checkPackage(Binder.getCallingUid(), packageName);
422 }
423
Ihab Awad98a55602014-06-30 21:27:28 -0700424 private void showCallScreenInternal(boolean showDialpad) {
425 CallsManager.getInstance().getInCallController().bringToForeground(showDialpad);
426 }
Ihab Awad60509462014-06-14 16:43:08 -0700427
Santos Cordon46592f02014-07-07 15:11:35 -0700428 private boolean isDefaultDialerCalling() {
429 ComponentName defaultDialerComponent = getDefaultPhoneApp();
430 if (defaultDialerComponent != null) {
431 try {
432 mAppOpsManager.checkPackage(
433 Binder.getCallingUid(), defaultDialerComponent.getPackageName());
434 return true;
435 } catch (SecurityException e) {
436 Log.e(TAG, e, "Could not get default dialer.");
437 }
438 }
439 return false;
440 }
441
442 private TelephonyManager getTelephonyManager() {
443 return (TelephonyManager)
444 TelecommApp.getInstance().getSystemService(Context.TELEPHONY_SERVICE);
445 }
446
Santos Cordon7da72ef2014-06-25 15:50:22 -0700447 private void publish() {
448 Log.d(this, "publish: %s", this);
449 ServiceManager.addService(SERVICE_NAME, this);
Ihab Awad60509462014-06-14 16:43:08 -0700450 }
Santos Cordon23baed32014-06-27 14:45:39 -0700451
452 private MainThreadRequest sendRequestAsync(int command, int arg1) {
453 MainThreadRequest request = new MainThreadRequest();
454 mMainThreadHandler.obtainMessage(command, arg1, 0, request).sendToTarget();
455 return request;
456 }
457
458 /**
459 * Posts the specified command to be executed on the main thread, waits for the request to
460 * complete, and returns the result.
461 */
462 private Object sendRequest(int command) {
463 if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
Sailesh Nepalbca199f2014-07-02 15:57:44 -0700464 MainThreadRequest request = new MainThreadRequest();
465 mMainThreadHandler.handleMessage(mMainThreadHandler.obtainMessage(command, request));
466 return request.result;
467 } else {
468 MainThreadRequest request = sendRequestAsync(command, 0);
Santos Cordon23baed32014-06-27 14:45:39 -0700469
Sailesh Nepalbca199f2014-07-02 15:57:44 -0700470 // Wait for the request to complete
471 synchronized (request) {
472 while (request.result == null) {
473 try {
474 request.wait();
475 } catch (InterruptedException e) {
476 // Do nothing, go back and wait until the request is complete
477 }
Santos Cordon23baed32014-06-27 14:45:39 -0700478 }
479 }
Sailesh Nepalbca199f2014-07-02 15:57:44 -0700480 return request.result;
Santos Cordon23baed32014-06-27 14:45:39 -0700481 }
Santos Cordon23baed32014-06-27 14:45:39 -0700482 }
Santos Cordonb64c1502014-05-21 21:21:49 -0700483}