blob: 9aa46873a7d087d5aae6b9034ed1ff68a870fa8c [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;
Santos Cordon46592f02014-07-07 15:11:35 -070032import android.telecomm.TelecommManager;
33import android.telephony.TelephonyManager;
34
35import com.android.internal.telecomm.ITelecommService;
36import com.google.android.collect.Lists;
Santos Cordonb64c1502014-05-21 21:21:49 -070037
Ihab Awad60509462014-06-14 16:43:08 -070038import java.util.List;
Santos Cordonb64c1502014-05-21 21:21:49 -070039
40/**
41 * Implementation of the ITelecomm interface.
42 */
43public class TelecommServiceImpl extends ITelecommService.Stub {
Santos Cordon23baed32014-06-27 14:45:39 -070044 /**
45 * A request object for use with {@link MainThreadHandler}. Requesters should wait() on the
46 * request after sending. The main thread will notify the request when it is complete.
47 */
48 private static final class MainThreadRequest {
49 /** The result of the request that is run on the main thread */
50 public Object result;
51 }
Santos Cordonb64c1502014-05-21 21:21:49 -070052
53 /**
54 * A handler that processes messages on the main thread in the phone process. Since many
55 * of the Phone calls are not thread safe this is needed to shuttle the requests from the
56 * inbound binder threads to the main thread in the phone process.
57 */
Santos Cordon23baed32014-06-27 14:45:39 -070058 private final class MainThreadHandler extends Handler {
Santos Cordonb64c1502014-05-21 21:21:49 -070059 @Override
60 public void handleMessage(Message msg) {
Santos Cordon23baed32014-06-27 14:45:39 -070061 if (msg.obj instanceof MainThreadRequest) {
62 MainThreadRequest request = (MainThreadRequest) msg.obj;
63 Object result = null;
64 switch (msg.what) {
65 case MSG_SILENCE_RINGER:
66 mCallsManager.getRinger().silence();
67 break;
68 case MSG_SHOW_CALL_SCREEN:
69 mCallsManager.getInCallController().bringToForeground(msg.arg1 == 1);
70 break;
71 case MSG_IS_IN_A_PHONE_CALL:
72 result = mCallsManager.hasAnyCalls();
73 break;
74 case MSG_IS_RINGING:
75 result = mCallsManager.hasRingingCall();
76 break;
77 case MSG_END_CALL:
78 result = endCallInternal();
79 break;
80 case MSG_ACCEPT_RINGING_CALL:
81 acceptRingingCallInternal();
82 break;
Santos Cordon64c7e962014-07-02 15:15:27 -070083 case MSG_CANCEL_MISSED_CALLS_NOTIFICATION:
84 mMissedCallNotifier.clearMissedCalls();
85 break;
Santos Cordon23baed32014-06-27 14:45:39 -070086 }
87
88 if (result != null) {
89 request.result = result;
90 synchronized(request) {
91 request.notifyAll();
92 }
93 }
Santos Cordonb64c1502014-05-21 21:21:49 -070094 }
95 }
Santos Cordon23baed32014-06-27 14:45:39 -070096 }
Santos Cordonb64c1502014-05-21 21:21:49 -070097
Santos Cordon7da72ef2014-06-25 15:50:22 -070098 /** Private constructor; @see init() */
Santos Cordon23baed32014-06-27 14:45:39 -070099 private static final String TAG = TelecommServiceImpl.class.getSimpleName();
100
101 private static final String SERVICE_NAME = "telecomm";
102
103 private static final int MSG_SILENCE_RINGER = 1;
104 private static final int MSG_SHOW_CALL_SCREEN = 2;
105 private static final int MSG_IS_IN_A_PHONE_CALL = 3;
106 private static final int MSG_IS_RINGING = 4;
107 private static final int MSG_END_CALL = 5;
108 private static final int MSG_ACCEPT_RINGING_CALL = 6;
Santos Cordon64c7e962014-07-02 15:15:27 -0700109 private static final int MSG_CANCEL_MISSED_CALLS_NOTIFICATION = 7;
Santos Cordon23baed32014-06-27 14:45:39 -0700110
111 /** The singleton instance. */
112 private static TelecommServiceImpl sInstance;
113
Santos Cordon46592f02014-07-07 15:11:35 -0700114 private final MainThreadHandler mMainThreadHandler = new MainThreadHandler();
Santos Cordon23baed32014-06-27 14:45:39 -0700115 private final CallsManager mCallsManager = CallsManager.getInstance();
Santos Cordon64c7e962014-07-02 15:15:27 -0700116 private final MissedCallNotifier mMissedCallNotifier;
Santos Cordon46592f02014-07-07 15:11:35 -0700117 private final AppOpsManager mAppOpsManager;
Santos Cordon23baed32014-06-27 14:45:39 -0700118
Santos Cordon64c7e962014-07-02 15:15:27 -0700119 private TelecommServiceImpl(MissedCallNotifier missedCallNotifier) {
120 mMissedCallNotifier = missedCallNotifier;
Santos Cordon46592f02014-07-07 15:11:35 -0700121 mAppOpsManager =
122 (AppOpsManager) TelecommApp.getInstance().getSystemService(Context.APP_OPS_SERVICE);
123
Santos Cordon7da72ef2014-06-25 15:50:22 -0700124 publish();
125 }
126
Santos Cordonb64c1502014-05-21 21:21:49 -0700127 /**
128 * Initialize the singleton TelecommServiceImpl instance.
129 * This is only done once, at startup, from TelecommApp.onCreate().
130 */
Santos Cordon64c7e962014-07-02 15:15:27 -0700131 static TelecommServiceImpl init(MissedCallNotifier missedCallNotifier) {
Santos Cordonb64c1502014-05-21 21:21:49 -0700132 synchronized (TelecommServiceImpl.class) {
133 if (sInstance == null) {
Santos Cordon64c7e962014-07-02 15:15:27 -0700134 sInstance = new TelecommServiceImpl(missedCallNotifier);
Santos Cordonb64c1502014-05-21 21:21:49 -0700135 } else {
136 Log.wtf(TAG, "init() called multiple times! sInstance %s", sInstance);
137 }
138 return sInstance;
139 }
140 }
141
Santos Cordonb64c1502014-05-21 21:21:49 -0700142 //
Santos Cordon23baed32014-06-27 14:45:39 -0700143 // Implementation of the ITelecommService interface.
Santos Cordonb64c1502014-05-21 21:21:49 -0700144 //
145
146 @Override
Ihab Awad98a55602014-06-30 21:27:28 -0700147 public List<PhoneAccount> getAccounts() {
148 // TODO (STOPSHIP): Static list of Accounts for testing and UX work only.
149 ComponentName componentName = new ComponentName(
150 "com.android.telecomm",
151 TelecommServiceImpl.class.getName()); // This field is a no-op
152 Context app = TelecommApp.getInstance();
153
154 return Lists.newArrayList(
155 new PhoneAccount(
156 componentName,
157 "account0",
158 Uri.parse("tel:999-555-1212"),
159 app.getString(R.string.test_account_0_label),
160 app.getString(R.string.test_account_0_short_description),
161 true,
162 true),
163 new PhoneAccount(
164 componentName,
165 "account1",
166 Uri.parse("tel:333-111-2222"),
167 app.getString(R.string.test_account_1_label),
168 app.getString(R.string.test_account_1_short_description),
169 true,
170 false),
171 new PhoneAccount(
172 componentName,
173 "account2",
174 Uri.parse("mailto:two@example.com"),
175 app.getString(R.string.test_account_2_label),
176 app.getString(R.string.test_account_2_short_description),
177 true,
178 false),
179 new PhoneAccount(
180 componentName,
181 "account3",
182 Uri.parse("mailto:three@example.com"),
183 app.getString(R.string.test_account_3_label),
184 app.getString(R.string.test_account_3_short_description),
185 true,
186 false)
187 );
Santos Cordon7da72ef2014-06-25 15:50:22 -0700188 }
189
190 @Override
Ihab Awad98a55602014-06-30 21:27:28 -0700191 public void setEnabled(PhoneAccount account, boolean enabled) {
Santos Cordon7da72ef2014-06-25 15:50:22 -0700192 // Enforce MODIFY_PHONE_STATE ?
193 // TODO
194 }
195
196 @Override
Ihab Awad98a55602014-06-30 21:27:28 -0700197 public void setSystemDefault(PhoneAccount account) {
Santos Cordon7da72ef2014-06-25 15:50:22 -0700198 // Enforce MODIFY_PHONE_STATE ?
199 // TODO
200 }
201
Santos Cordon23baed32014-06-27 14:45:39 -0700202 /**
203 * @see TelecommManager#silenceringer
204 */
Santos Cordon7da72ef2014-06-25 15:50:22 -0700205 @Override
Santos Cordonb64c1502014-05-21 21:21:49 -0700206 public void silenceRinger() {
207 Log.d(this, "silenceRinger");
Santos Cordonb64c1502014-05-21 21:21:49 -0700208 enforceModifyPermission();
Santos Cordon23baed32014-06-27 14:45:39 -0700209 sendRequestAsync(MSG_SILENCE_RINGER, 0);
Santos Cordonb64c1502014-05-21 21:21:49 -0700210 }
211
Santos Cordon23baed32014-06-27 14:45:39 -0700212 /**
213 * @see TelecommManager#getDefaultPhoneApp
214 */
Santos Cordon7da72ef2014-06-25 15:50:22 -0700215 @Override
216 public ComponentName getDefaultPhoneApp() {
217 Resources resources = TelecommApp.getInstance().getResources();
218 return new ComponentName(
219 resources.getString(R.string.ui_default_package),
220 resources.getString(R.string.dialer_default_class));
221 }
222
Santos Cordon23baed32014-06-27 14:45:39 -0700223 /**
224 * @see TelecommManager#isInAPhoneCall
225 */
226 @Override
227 public boolean isInAPhoneCall() {
228 enforceReadPermission();
229 return (boolean) sendRequest(MSG_IS_IN_A_PHONE_CALL);
230 }
231
232 /**
233 * @see TelecommManager#isRinging
234 */
235 @Override
236 public boolean isRinging() {
237 enforceReadPermission();
238 return (boolean) sendRequest(MSG_IS_RINGING);
239 }
240
241 /**
242 * @see TelecommManager#endCall
243 */
244 @Override
245 public boolean endCall() {
246 enforceModifyPermission();
247 return (boolean) sendRequest(MSG_END_CALL);
248 }
249
250 /**
251 * @see TelecommManager#acceptRingingCall
252 */
253 @Override
254 public void acceptRingingCall() {
255 enforceModifyPermission();
256 sendRequestAsync(MSG_ACCEPT_RINGING_CALL, 0);
257 }
258
Santos Cordon46592f02014-07-07 15:11:35 -0700259 /**
260 * @see PhoneManager#showCallScreen
261 */
Santos Cordon23baed32014-06-27 14:45:39 -0700262 @Override
263 public void showCallScreen(boolean showDialpad) {
Santos Cordon46592f02014-07-07 15:11:35 -0700264 enforceReadPermissionOrDefaultDialer();
Santos Cordon64c7e962014-07-02 15:15:27 -0700265 sendRequestAsync(MSG_SHOW_CALL_SCREEN, showDialpad ? 1 : 0);
266 }
267
Santos Cordon46592f02014-07-07 15:11:35 -0700268 /**
269 * @see PhoneManager#cancelMissedCallsNotification
270 */
Santos Cordon64c7e962014-07-02 15:15:27 -0700271 @Override
272 public void cancelMissedCallsNotification() {
Santos Cordon46592f02014-07-07 15:11:35 -0700273 enforceModifyPermissionOrDefaultDialer();
Santos Cordon64c7e962014-07-02 15:15:27 -0700274 sendRequestAsync(MSG_CANCEL_MISSED_CALLS_NOTIFICATION, 0);
Santos Cordon23baed32014-06-27 14:45:39 -0700275 }
276
Santos Cordon46592f02014-07-07 15:11:35 -0700277 /**
278 * @see PhoneManager#handlePinMmi
279 */
280 @Override
281 public boolean handlePinMmi(String dialString) {
282 enforceModifyPermissionOrDefaultDialer();
283
284 // Switch identity so that TelephonyManager checks Telecomm's permissions instead.
285 long token = Binder.clearCallingIdentity();
286 boolean retval = getTelephonyManager().handlePinMmi(dialString);
287 Binder.restoreCallingIdentity(token);
288
289 return retval;
290 }
291
Santos Cordon7da72ef2014-06-25 15:50:22 -0700292 //
Santos Cordon46592f02014-07-07 15:11:35 -0700293 // Supporting methods for the ITelecommService interface implementation.
Santos Cordon7da72ef2014-06-25 15:50:22 -0700294 //
295
Santos Cordon23baed32014-06-27 14:45:39 -0700296 private void acceptRingingCallInternal() {
297 Call call = mCallsManager.getFirstCallWithState(CallState.RINGING);
298 if (call != null) {
299 call.answer();
300 }
301 }
302
303 private boolean endCallInternal() {
304 // Always operate on the foreground call if one exists, otherwise get the first call in
305 // priority order by call-state.
306 Call call = mCallsManager.getForegroundCall();
307 if (call == null) {
308 call = mCallsManager.getFirstCallWithState(
309 CallState.ACTIVE,
310 CallState.DIALING,
311 CallState.RINGING,
312 CallState.ON_HOLD);
313 }
314
315 if (call != null) {
316 if (call.getState() == CallState.RINGING) {
317 call.reject(false /* rejectWithMessage */, null);
318 } else {
319 call.disconnect();
320 }
321 return true;
322 }
323
324 return false;
Santos Cordonb64c1502014-05-21 21:21:49 -0700325 }
326
327 /**
328 * Make sure the caller has the MODIFY_PHONE_STATE permission.
329 *
330 * @throws SecurityException if the caller does not have the required permission
331 */
332 private void enforceModifyPermission() {
333 TelecommApp.getInstance().enforceCallingOrSelfPermission(
334 android.Manifest.permission.MODIFY_PHONE_STATE, null);
335 }
Santos Cordonf3671a62014-05-29 21:51:53 -0700336
Santos Cordon46592f02014-07-07 15:11:35 -0700337 private void enforceModifyPermissionOrDefaultDialer() {
338 if (!isDefaultDialerCalling()) {
339 enforceModifyPermission();
340 }
341 }
342
Santos Cordon23baed32014-06-27 14:45:39 -0700343 private void enforceReadPermission() {
344 TelecommApp.getInstance().enforceCallingOrSelfPermission(
345 android.Manifest.permission.READ_PHONE_STATE, null);
Santos Cordonf3671a62014-05-29 21:51:53 -0700346 }
Yorke Leeceb96c92014-06-11 16:34:44 -0700347
Santos Cordon46592f02014-07-07 15:11:35 -0700348 private void enforceReadPermissionOrDefaultDialer() {
349 if (!isDefaultDialerCalling()) {
350 enforceReadPermission();
351 }
352 }
353
Ihab Awad98a55602014-06-30 21:27:28 -0700354 private void showCallScreenInternal(boolean showDialpad) {
355 CallsManager.getInstance().getInCallController().bringToForeground(showDialpad);
356 }
Ihab Awad60509462014-06-14 16:43:08 -0700357
Santos Cordon46592f02014-07-07 15:11:35 -0700358 private boolean isDefaultDialerCalling() {
359 ComponentName defaultDialerComponent = getDefaultPhoneApp();
360 if (defaultDialerComponent != null) {
361 try {
362 mAppOpsManager.checkPackage(
363 Binder.getCallingUid(), defaultDialerComponent.getPackageName());
364 return true;
365 } catch (SecurityException e) {
366 Log.e(TAG, e, "Could not get default dialer.");
367 }
368 }
369 return false;
370 }
371
372 private TelephonyManager getTelephonyManager() {
373 return (TelephonyManager)
374 TelecommApp.getInstance().getSystemService(Context.TELEPHONY_SERVICE);
375 }
376
Santos Cordon7da72ef2014-06-25 15:50:22 -0700377 private void publish() {
378 Log.d(this, "publish: %s", this);
379 ServiceManager.addService(SERVICE_NAME, this);
Ihab Awad60509462014-06-14 16:43:08 -0700380 }
Santos Cordon23baed32014-06-27 14:45:39 -0700381
382 private MainThreadRequest sendRequestAsync(int command, int arg1) {
383 MainThreadRequest request = new MainThreadRequest();
384 mMainThreadHandler.obtainMessage(command, arg1, 0, request).sendToTarget();
385 return request;
386 }
387
388 /**
389 * Posts the specified command to be executed on the main thread, waits for the request to
390 * complete, and returns the result.
391 */
392 private Object sendRequest(int command) {
393 if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
Sailesh Nepalbca199f2014-07-02 15:57:44 -0700394 MainThreadRequest request = new MainThreadRequest();
395 mMainThreadHandler.handleMessage(mMainThreadHandler.obtainMessage(command, request));
396 return request.result;
397 } else {
398 MainThreadRequest request = sendRequestAsync(command, 0);
Santos Cordon23baed32014-06-27 14:45:39 -0700399
Sailesh Nepalbca199f2014-07-02 15:57:44 -0700400 // Wait for the request to complete
401 synchronized (request) {
402 while (request.result == null) {
403 try {
404 request.wait();
405 } catch (InterruptedException e) {
406 // Do nothing, go back and wait until the request is complete
407 }
Santos Cordon23baed32014-06-27 14:45:39 -0700408 }
409 }
Sailesh Nepalbca199f2014-07-02 15:57:44 -0700410 return request.result;
Santos Cordon23baed32014-06-27 14:45:39 -0700411 }
Santos Cordon23baed32014-06-27 14:45:39 -0700412 }
Santos Cordonb64c1502014-05-21 21:21:49 -0700413}