blob: f1ab6c0d61307adfe03144348388f49b8da86c8e [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;
Santos Cordon23baed32014-06-27 14:45:39 -070071 case MSG_END_CALL:
72 result = endCallInternal();
73 break;
74 case MSG_ACCEPT_RINGING_CALL:
75 acceptRingingCallInternal();
76 break;
Santos Cordon64c7e962014-07-02 15:15:27 -070077 case MSG_CANCEL_MISSED_CALLS_NOTIFICATION:
78 mMissedCallNotifier.clearMissedCalls();
79 break;
Santos Cordon23baed32014-06-27 14:45:39 -070080 }
81
82 if (result != null) {
83 request.result = result;
84 synchronized(request) {
85 request.notifyAll();
86 }
87 }
Santos Cordonb64c1502014-05-21 21:21:49 -070088 }
89 }
Santos Cordon23baed32014-06-27 14:45:39 -070090 }
Santos Cordonb64c1502014-05-21 21:21:49 -070091
Santos Cordon7da72ef2014-06-25 15:50:22 -070092 /** Private constructor; @see init() */
Santos Cordon23baed32014-06-27 14:45:39 -070093 private static final String TAG = TelecommServiceImpl.class.getSimpleName();
94
95 private static final String SERVICE_NAME = "telecomm";
96
97 private static final int MSG_SILENCE_RINGER = 1;
98 private static final int MSG_SHOW_CALL_SCREEN = 2;
Santos Cordon626697a2014-07-10 22:36:37 -070099 private static final int MSG_END_CALL = 3;
100 private static final int MSG_ACCEPT_RINGING_CALL = 4;
101 private static final int MSG_CANCEL_MISSED_CALLS_NOTIFICATION = 5;
Santos Cordon23baed32014-06-27 14:45:39 -0700102
103 /** The singleton instance. */
104 private static TelecommServiceImpl sInstance;
105
Santos Cordon46592f02014-07-07 15:11:35 -0700106 private final MainThreadHandler mMainThreadHandler = new MainThreadHandler();
Santos Cordon23baed32014-06-27 14:45:39 -0700107 private final CallsManager mCallsManager = CallsManager.getInstance();
Santos Cordon64c7e962014-07-02 15:15:27 -0700108 private final MissedCallNotifier mMissedCallNotifier;
Santos Cordon46592f02014-07-07 15:11:35 -0700109 private final AppOpsManager mAppOpsManager;
Santos Cordon23baed32014-06-27 14:45:39 -0700110
Santos Cordon64c7e962014-07-02 15:15:27 -0700111 private TelecommServiceImpl(MissedCallNotifier missedCallNotifier) {
112 mMissedCallNotifier = missedCallNotifier;
Santos Cordon46592f02014-07-07 15:11:35 -0700113 mAppOpsManager =
114 (AppOpsManager) TelecommApp.getInstance().getSystemService(Context.APP_OPS_SERVICE);
115
Santos Cordon7da72ef2014-06-25 15:50:22 -0700116 publish();
117 }
118
Santos Cordonb64c1502014-05-21 21:21:49 -0700119 /**
120 * Initialize the singleton TelecommServiceImpl instance.
121 * This is only done once, at startup, from TelecommApp.onCreate().
122 */
Santos Cordon64c7e962014-07-02 15:15:27 -0700123 static TelecommServiceImpl init(MissedCallNotifier missedCallNotifier) {
Santos Cordonb64c1502014-05-21 21:21:49 -0700124 synchronized (TelecommServiceImpl.class) {
125 if (sInstance == null) {
Santos Cordon64c7e962014-07-02 15:15:27 -0700126 sInstance = new TelecommServiceImpl(missedCallNotifier);
Santos Cordonb64c1502014-05-21 21:21:49 -0700127 } else {
128 Log.wtf(TAG, "init() called multiple times! sInstance %s", sInstance);
129 }
130 return sInstance;
131 }
132 }
133
Santos Cordonb64c1502014-05-21 21:21:49 -0700134 //
Santos Cordon23baed32014-06-27 14:45:39 -0700135 // Implementation of the ITelecommService interface.
Santos Cordonb64c1502014-05-21 21:21:49 -0700136 //
137
138 @Override
Ihab Awad98a55602014-06-30 21:27:28 -0700139 public List<PhoneAccount> getAccounts() {
140 // TODO (STOPSHIP): Static list of Accounts for testing and UX work only.
141 ComponentName componentName = new ComponentName(
142 "com.android.telecomm",
143 TelecommServiceImpl.class.getName()); // This field is a no-op
144 Context app = TelecommApp.getInstance();
145
146 return Lists.newArrayList(
147 new PhoneAccount(
148 componentName,
149 "account0",
150 Uri.parse("tel:999-555-1212"),
151 app.getString(R.string.test_account_0_label),
152 app.getString(R.string.test_account_0_short_description),
153 true,
154 true),
155 new PhoneAccount(
156 componentName,
157 "account1",
158 Uri.parse("tel:333-111-2222"),
159 app.getString(R.string.test_account_1_label),
160 app.getString(R.string.test_account_1_short_description),
161 true,
162 false),
163 new PhoneAccount(
164 componentName,
165 "account2",
166 Uri.parse("mailto:two@example.com"),
167 app.getString(R.string.test_account_2_label),
168 app.getString(R.string.test_account_2_short_description),
169 true,
170 false),
171 new PhoneAccount(
172 componentName,
173 "account3",
174 Uri.parse("mailto:three@example.com"),
175 app.getString(R.string.test_account_3_label),
176 app.getString(R.string.test_account_3_short_description),
177 true,
178 false)
179 );
Santos Cordon7da72ef2014-06-25 15:50:22 -0700180 }
181
182 @Override
Ihab Awad98a55602014-06-30 21:27:28 -0700183 public void setEnabled(PhoneAccount account, boolean enabled) {
Santos Cordon7da72ef2014-06-25 15:50:22 -0700184 // Enforce MODIFY_PHONE_STATE ?
185 // TODO
186 }
187
188 @Override
Ihab Awad98a55602014-06-30 21:27:28 -0700189 public void setSystemDefault(PhoneAccount account) {
Santos Cordon7da72ef2014-06-25 15:50:22 -0700190 // Enforce MODIFY_PHONE_STATE ?
191 // TODO
192 }
193
Santos Cordon23baed32014-06-27 14:45:39 -0700194 /**
195 * @see TelecommManager#silenceringer
196 */
Santos Cordon7da72ef2014-06-25 15:50:22 -0700197 @Override
Santos Cordonb64c1502014-05-21 21:21:49 -0700198 public void silenceRinger() {
199 Log.d(this, "silenceRinger");
Santos Cordonb64c1502014-05-21 21:21:49 -0700200 enforceModifyPermission();
Santos Cordon23baed32014-06-27 14:45:39 -0700201 sendRequestAsync(MSG_SILENCE_RINGER, 0);
Santos Cordonb64c1502014-05-21 21:21:49 -0700202 }
203
Santos Cordon23baed32014-06-27 14:45:39 -0700204 /**
205 * @see TelecommManager#getDefaultPhoneApp
206 */
Santos Cordon7da72ef2014-06-25 15:50:22 -0700207 @Override
208 public ComponentName getDefaultPhoneApp() {
209 Resources resources = TelecommApp.getInstance().getResources();
210 return new ComponentName(
211 resources.getString(R.string.ui_default_package),
212 resources.getString(R.string.dialer_default_class));
213 }
214
Santos Cordon23baed32014-06-27 14:45:39 -0700215 /**
216 * @see TelecommManager#isInAPhoneCall
217 */
218 @Override
219 public boolean isInAPhoneCall() {
220 enforceReadPermission();
Santos Cordon626697a2014-07-10 22:36:37 -0700221 // Do not use sendRequest() with this method since it could cause a deadlock with
222 // audio service, which we call into from the main thread: AudioManager.setMode().
223 return mCallsManager.hasAnyCalls();
Santos Cordon23baed32014-06-27 14:45:39 -0700224 }
225
226 /**
227 * @see TelecommManager#isRinging
228 */
229 @Override
230 public boolean isRinging() {
231 enforceReadPermission();
Santos Cordon626697a2014-07-10 22:36:37 -0700232 return mCallsManager.hasRingingCall();
Santos Cordon23baed32014-06-27 14:45:39 -0700233 }
234
235 /**
236 * @see TelecommManager#endCall
237 */
238 @Override
239 public boolean endCall() {
240 enforceModifyPermission();
241 return (boolean) sendRequest(MSG_END_CALL);
242 }
243
244 /**
245 * @see TelecommManager#acceptRingingCall
246 */
247 @Override
248 public void acceptRingingCall() {
249 enforceModifyPermission();
250 sendRequestAsync(MSG_ACCEPT_RINGING_CALL, 0);
251 }
252
Santos Cordon46592f02014-07-07 15:11:35 -0700253 /**
254 * @see PhoneManager#showCallScreen
255 */
Santos Cordon23baed32014-06-27 14:45:39 -0700256 @Override
257 public void showCallScreen(boolean showDialpad) {
Santos Cordon46592f02014-07-07 15:11:35 -0700258 enforceReadPermissionOrDefaultDialer();
Santos Cordon64c7e962014-07-02 15:15:27 -0700259 sendRequestAsync(MSG_SHOW_CALL_SCREEN, showDialpad ? 1 : 0);
260 }
261
Santos Cordon46592f02014-07-07 15:11:35 -0700262 /**
263 * @see PhoneManager#cancelMissedCallsNotification
264 */
Santos Cordon64c7e962014-07-02 15:15:27 -0700265 @Override
266 public void cancelMissedCallsNotification() {
Santos Cordon46592f02014-07-07 15:11:35 -0700267 enforceModifyPermissionOrDefaultDialer();
Santos Cordon64c7e962014-07-02 15:15:27 -0700268 sendRequestAsync(MSG_CANCEL_MISSED_CALLS_NOTIFICATION, 0);
Santos Cordon23baed32014-06-27 14:45:39 -0700269 }
270
Santos Cordon46592f02014-07-07 15:11:35 -0700271 /**
272 * @see PhoneManager#handlePinMmi
273 */
274 @Override
275 public boolean handlePinMmi(String dialString) {
276 enforceModifyPermissionOrDefaultDialer();
277
278 // Switch identity so that TelephonyManager checks Telecomm's permissions instead.
279 long token = Binder.clearCallingIdentity();
280 boolean retval = getTelephonyManager().handlePinMmi(dialString);
281 Binder.restoreCallingIdentity(token);
282
283 return retval;
284 }
285
Santos Cordon7da72ef2014-06-25 15:50:22 -0700286 //
Santos Cordon46592f02014-07-07 15:11:35 -0700287 // Supporting methods for the ITelecommService interface implementation.
Santos Cordon7da72ef2014-06-25 15:50:22 -0700288 //
289
Santos Cordon23baed32014-06-27 14:45:39 -0700290 private void acceptRingingCallInternal() {
291 Call call = mCallsManager.getFirstCallWithState(CallState.RINGING);
292 if (call != null) {
293 call.answer();
294 }
295 }
296
297 private boolean endCallInternal() {
298 // Always operate on the foreground call if one exists, otherwise get the first call in
299 // priority order by call-state.
300 Call call = mCallsManager.getForegroundCall();
301 if (call == null) {
302 call = mCallsManager.getFirstCallWithState(
303 CallState.ACTIVE,
304 CallState.DIALING,
305 CallState.RINGING,
306 CallState.ON_HOLD);
307 }
308
309 if (call != null) {
310 if (call.getState() == CallState.RINGING) {
311 call.reject(false /* rejectWithMessage */, null);
312 } else {
313 call.disconnect();
314 }
315 return true;
316 }
317
318 return false;
Santos Cordonb64c1502014-05-21 21:21:49 -0700319 }
320
321 /**
322 * Make sure the caller has the MODIFY_PHONE_STATE permission.
323 *
324 * @throws SecurityException if the caller does not have the required permission
325 */
326 private void enforceModifyPermission() {
327 TelecommApp.getInstance().enforceCallingOrSelfPermission(
328 android.Manifest.permission.MODIFY_PHONE_STATE, null);
329 }
Santos Cordonf3671a62014-05-29 21:51:53 -0700330
Santos Cordon46592f02014-07-07 15:11:35 -0700331 private void enforceModifyPermissionOrDefaultDialer() {
332 if (!isDefaultDialerCalling()) {
333 enforceModifyPermission();
334 }
335 }
336
Santos Cordon23baed32014-06-27 14:45:39 -0700337 private void enforceReadPermission() {
338 TelecommApp.getInstance().enforceCallingOrSelfPermission(
339 android.Manifest.permission.READ_PHONE_STATE, null);
Santos Cordonf3671a62014-05-29 21:51:53 -0700340 }
Yorke Leeceb96c92014-06-11 16:34:44 -0700341
Santos Cordon46592f02014-07-07 15:11:35 -0700342 private void enforceReadPermissionOrDefaultDialer() {
343 if (!isDefaultDialerCalling()) {
344 enforceReadPermission();
345 }
346 }
347
Ihab Awad98a55602014-06-30 21:27:28 -0700348 private void showCallScreenInternal(boolean showDialpad) {
349 CallsManager.getInstance().getInCallController().bringToForeground(showDialpad);
350 }
Ihab Awad60509462014-06-14 16:43:08 -0700351
Santos Cordon46592f02014-07-07 15:11:35 -0700352 private boolean isDefaultDialerCalling() {
353 ComponentName defaultDialerComponent = getDefaultPhoneApp();
354 if (defaultDialerComponent != null) {
355 try {
356 mAppOpsManager.checkPackage(
357 Binder.getCallingUid(), defaultDialerComponent.getPackageName());
358 return true;
359 } catch (SecurityException e) {
360 Log.e(TAG, e, "Could not get default dialer.");
361 }
362 }
363 return false;
364 }
365
366 private TelephonyManager getTelephonyManager() {
367 return (TelephonyManager)
368 TelecommApp.getInstance().getSystemService(Context.TELEPHONY_SERVICE);
369 }
370
Santos Cordon7da72ef2014-06-25 15:50:22 -0700371 private void publish() {
372 Log.d(this, "publish: %s", this);
373 ServiceManager.addService(SERVICE_NAME, this);
Ihab Awad60509462014-06-14 16:43:08 -0700374 }
Santos Cordon23baed32014-06-27 14:45:39 -0700375
376 private MainThreadRequest sendRequestAsync(int command, int arg1) {
377 MainThreadRequest request = new MainThreadRequest();
378 mMainThreadHandler.obtainMessage(command, arg1, 0, request).sendToTarget();
379 return request;
380 }
381
382 /**
383 * Posts the specified command to be executed on the main thread, waits for the request to
384 * complete, and returns the result.
385 */
386 private Object sendRequest(int command) {
387 if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
Sailesh Nepalbca199f2014-07-02 15:57:44 -0700388 MainThreadRequest request = new MainThreadRequest();
389 mMainThreadHandler.handleMessage(mMainThreadHandler.obtainMessage(command, request));
390 return request.result;
391 } else {
392 MainThreadRequest request = sendRequestAsync(command, 0);
Santos Cordon23baed32014-06-27 14:45:39 -0700393
Sailesh Nepalbca199f2014-07-02 15:57:44 -0700394 // Wait for the request to complete
395 synchronized (request) {
396 while (request.result == null) {
397 try {
398 request.wait();
399 } catch (InterruptedException e) {
400 // Do nothing, go back and wait until the request is complete
401 }
Santos Cordon23baed32014-06-27 14:45:39 -0700402 }
403 }
Sailesh Nepalbca199f2014-07-02 15:57:44 -0700404 return request.result;
Santos Cordon23baed32014-06-27 14:45:39 -0700405 }
Santos Cordon23baed32014-06-27 14:45:39 -0700406 }
Santos Cordonb64c1502014-05-21 21:21:49 -0700407}