blob: 24d6cc42052ab75a79e6f9049414f154df53a37a [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
Ihab Awad60509462014-06-14 16:43:08 -070019import com.google.android.collect.Lists;
20
21import com.android.internal.telecomm.ITelecommService;
22
Yorke Leeceb96c92014-06-11 16:34:44 -070023import android.content.ComponentName;
Yorke Leeceb96c92014-06-11 16:34:44 -070024import android.content.res.Resources;
Ihab Awad60509462014-06-14 16:43:08 -070025import android.net.Uri;
Santos Cordonb64c1502014-05-21 21:21:49 -070026import android.os.Handler;
Santos Cordon23baed32014-06-27 14:45:39 -070027import android.os.Looper;
Santos Cordonb64c1502014-05-21 21:21:49 -070028import android.os.Message;
29import android.os.ServiceManager;
Santos Cordon23baed32014-06-27 14:45:39 -070030import android.telecomm.CallState;
Ihab Awad60509462014-06-14 16:43:08 -070031import android.telecomm.Subscription;
Yorke Leeceb96c92014-06-11 16:34:44 -070032import android.text.TextUtils;
Santos Cordonb64c1502014-05-21 21:21:49 -070033
Ihab Awad60509462014-06-14 16:43:08 -070034import java.util.List;
Santos Cordonb64c1502014-05-21 21:21:49 -070035
36/**
37 * Implementation of the ITelecomm interface.
38 */
39public class TelecommServiceImpl extends ITelecommService.Stub {
Santos Cordon23baed32014-06-27 14:45:39 -070040 /**
41 * A request object for use with {@link MainThreadHandler}. Requesters should wait() on the
42 * request after sending. The main thread will notify the request when it is complete.
43 */
44 private static final class MainThreadRequest {
45 /** The result of the request that is run on the main thread */
46 public Object result;
47 }
Santos Cordonb64c1502014-05-21 21:21:49 -070048
49 /**
50 * A handler that processes messages on the main thread in the phone process. Since many
51 * of the Phone calls are not thread safe this is needed to shuttle the requests from the
52 * inbound binder threads to the main thread in the phone process.
53 */
Santos Cordon23baed32014-06-27 14:45:39 -070054 private final class MainThreadHandler extends Handler {
Santos Cordonb64c1502014-05-21 21:21:49 -070055 @Override
56 public void handleMessage(Message msg) {
Santos Cordon23baed32014-06-27 14:45:39 -070057 if (msg.obj instanceof MainThreadRequest) {
58 MainThreadRequest request = (MainThreadRequest) msg.obj;
59 Object result = null;
60 switch (msg.what) {
61 case MSG_SILENCE_RINGER:
62 mCallsManager.getRinger().silence();
63 break;
64 case MSG_SHOW_CALL_SCREEN:
65 mCallsManager.getInCallController().bringToForeground(msg.arg1 == 1);
66 break;
67 case MSG_IS_IN_A_PHONE_CALL:
68 result = mCallsManager.hasAnyCalls();
69 break;
70 case MSG_IS_RINGING:
71 result = mCallsManager.hasRingingCall();
72 break;
73 case MSG_END_CALL:
74 result = endCallInternal();
75 break;
76 case MSG_ACCEPT_RINGING_CALL:
77 acceptRingingCallInternal();
78 break;
79 }
80
81 if (result != null) {
82 request.result = result;
83 synchronized(request) {
84 request.notifyAll();
85 }
86 }
Santos Cordonb64c1502014-05-21 21:21:49 -070087 }
88 }
Santos Cordon23baed32014-06-27 14:45:39 -070089 }
Santos Cordonb64c1502014-05-21 21:21:49 -070090
Santos Cordon7da72ef2014-06-25 15:50:22 -070091 /** Private constructor; @see init() */
Santos Cordon23baed32014-06-27 14:45:39 -070092 private static final String TAG = TelecommServiceImpl.class.getSimpleName();
93
94 private static final String SERVICE_NAME = "telecomm";
95
96 private static final int MSG_SILENCE_RINGER = 1;
97 private static final int MSG_SHOW_CALL_SCREEN = 2;
98 private static final int MSG_IS_IN_A_PHONE_CALL = 3;
99 private static final int MSG_IS_RINGING = 4;
100 private static final int MSG_END_CALL = 5;
101 private static final int MSG_ACCEPT_RINGING_CALL = 6;
102
103 /** The singleton instance. */
104 private static TelecommServiceImpl sInstance;
105
106 private final CallsManager mCallsManager = CallsManager.getInstance();
107
108 private final MainThreadHandler mMainThreadHandler = new MainThreadHandler();
109
Santos Cordon7da72ef2014-06-25 15:50:22 -0700110 private TelecommServiceImpl() {
111 publish();
112 }
113
Santos Cordonb64c1502014-05-21 21:21:49 -0700114 /**
115 * Initialize the singleton TelecommServiceImpl instance.
116 * This is only done once, at startup, from TelecommApp.onCreate().
117 */
118 static TelecommServiceImpl init() {
119 synchronized (TelecommServiceImpl.class) {
120 if (sInstance == null) {
121 sInstance = new TelecommServiceImpl();
122 } else {
123 Log.wtf(TAG, "init() called multiple times! sInstance %s", sInstance);
124 }
125 return sInstance;
126 }
127 }
128
Santos Cordonb64c1502014-05-21 21:21:49 -0700129 //
Santos Cordon23baed32014-06-27 14:45:39 -0700130 // Implementation of the ITelecommService interface.
Santos Cordonb64c1502014-05-21 21:21:49 -0700131 //
132
133 @Override
Santos Cordon7da72ef2014-06-25 15:50:22 -0700134 public List<Subscription> getSubscriptions() {
135 return sSubscriptions;
136 }
137
138 @Override
139 public void setEnabled(Subscription subscription, boolean enabled) {
140 // Enforce MODIFY_PHONE_STATE ?
141 // TODO
142 }
143
144 @Override
145 public void setSystemDefault(Subscription subscription) {
146 // Enforce MODIFY_PHONE_STATE ?
147 // TODO
148 }
149
Santos Cordon23baed32014-06-27 14:45:39 -0700150 /**
151 * @see TelecommManager#silenceringer
152 */
Santos Cordon7da72ef2014-06-25 15:50:22 -0700153 @Override
Santos Cordonb64c1502014-05-21 21:21:49 -0700154 public void silenceRinger() {
155 Log.d(this, "silenceRinger");
Santos Cordonb64c1502014-05-21 21:21:49 -0700156 enforceModifyPermission();
Santos Cordon23baed32014-06-27 14:45:39 -0700157 sendRequestAsync(MSG_SILENCE_RINGER, 0);
Santos Cordonb64c1502014-05-21 21:21:49 -0700158 }
159
Santos Cordon23baed32014-06-27 14:45:39 -0700160 /**
161 * @see TelecommManager#getDefaultPhoneApp
162 */
Santos Cordon7da72ef2014-06-25 15:50:22 -0700163 @Override
164 public ComponentName getDefaultPhoneApp() {
165 Resources resources = TelecommApp.getInstance().getResources();
166 return new ComponentName(
167 resources.getString(R.string.ui_default_package),
168 resources.getString(R.string.dialer_default_class));
169 }
170
Santos Cordon23baed32014-06-27 14:45:39 -0700171 /**
172 * @see TelecommManager#isInAPhoneCall
173 */
174 @Override
175 public boolean isInAPhoneCall() {
176 enforceReadPermission();
177 return (boolean) sendRequest(MSG_IS_IN_A_PHONE_CALL);
178 }
179
180 /**
181 * @see TelecommManager#isRinging
182 */
183 @Override
184 public boolean isRinging() {
185 enforceReadPermission();
186 return (boolean) sendRequest(MSG_IS_RINGING);
187 }
188
189 /**
190 * @see TelecommManager#endCall
191 */
192 @Override
193 public boolean endCall() {
194 enforceModifyPermission();
195 return (boolean) sendRequest(MSG_END_CALL);
196 }
197
198 /**
199 * @see TelecommManager#acceptRingingCall
200 */
201 @Override
202 public void acceptRingingCall() {
203 enforceModifyPermission();
204 sendRequestAsync(MSG_ACCEPT_RINGING_CALL, 0);
205 }
206
207 @Override
208 public void showCallScreen(boolean showDialpad) {
209 mMainThreadHandler.obtainMessage(MSG_SHOW_CALL_SCREEN, showDialpad ? 1 : 0, 0)
210 .sendToTarget();
211 }
212
Santos Cordon7da72ef2014-06-25 15:50:22 -0700213 //
214 // Supporting methods for the ITelephony interface implementation.
215 //
216
Santos Cordon23baed32014-06-27 14:45:39 -0700217 private void acceptRingingCallInternal() {
218 Call call = mCallsManager.getFirstCallWithState(CallState.RINGING);
219 if (call != null) {
220 call.answer();
221 }
222 }
223
224 private boolean endCallInternal() {
225 // Always operate on the foreground call if one exists, otherwise get the first call in
226 // priority order by call-state.
227 Call call = mCallsManager.getForegroundCall();
228 if (call == null) {
229 call = mCallsManager.getFirstCallWithState(
230 CallState.ACTIVE,
231 CallState.DIALING,
232 CallState.RINGING,
233 CallState.ON_HOLD);
234 }
235
236 if (call != null) {
237 if (call.getState() == CallState.RINGING) {
238 call.reject(false /* rejectWithMessage */, null);
239 } else {
240 call.disconnect();
241 }
242 return true;
243 }
244
245 return false;
Santos Cordonb64c1502014-05-21 21:21:49 -0700246 }
247
248 /**
249 * Make sure the caller has the MODIFY_PHONE_STATE permission.
250 *
251 * @throws SecurityException if the caller does not have the required permission
252 */
253 private void enforceModifyPermission() {
254 TelecommApp.getInstance().enforceCallingOrSelfPermission(
255 android.Manifest.permission.MODIFY_PHONE_STATE, null);
256 }
Santos Cordonf3671a62014-05-29 21:51:53 -0700257
Santos Cordon23baed32014-06-27 14:45:39 -0700258 private void enforceReadPermission() {
259 TelecommApp.getInstance().enforceCallingOrSelfPermission(
260 android.Manifest.permission.READ_PHONE_STATE, null);
Santos Cordonf3671a62014-05-29 21:51:53 -0700261 }
Yorke Leeceb96c92014-06-11 16:34:44 -0700262
Ihab Awad60509462014-06-14 16:43:08 -0700263 // TODO (STOPSHIP): Static list of Subscriptions for testing and UX work only.
264
265 private static final ComponentName sComponentName = new ComponentName(
266 "com.android.telecomm",
267 TelecommServiceImpl.class.getName()); // This field is a no-op
268
269 private static final List<Subscription> sSubscriptions = Lists.newArrayList(
270 new Subscription(
271 sComponentName,
272 "subscription0",
273 Uri.parse("tel:999-555-1212"),
274 R.string.test_subscription_0_label,
275 R.string.test_subscription_0_short_description,
276 R.drawable.q_mobile,
277 true,
278 true),
279 new Subscription(
280 sComponentName,
281 "subscription1",
282 Uri.parse("tel:333-111-2222"),
283 R.string.test_subscription_1_label,
284 R.string.test_subscription_1_short_description,
285 R.drawable.market_wireless,
286 true,
287 false),
288 new Subscription(
289 sComponentName,
290 "subscription2",
291 Uri.parse("mailto:two@example.com"),
292 R.string.test_subscription_2_label,
293 R.string.test_subscription_2_short_description,
294 R.drawable.talk_to_your_circles,
295 true,
296 false),
297 new Subscription(
298 sComponentName,
299 "subscription3",
300 Uri.parse("mailto:three@example.com"),
301 R.string.test_subscription_3_label,
302 R.string.test_subscription_3_short_description,
303 R.drawable.chat_with_others,
304 true,
305 false)
306 );
307
Santos Cordon7da72ef2014-06-25 15:50:22 -0700308 private void publish() {
309 Log.d(this, "publish: %s", this);
310 ServiceManager.addService(SERVICE_NAME, this);
Ihab Awad60509462014-06-14 16:43:08 -0700311 }
Santos Cordon23baed32014-06-27 14:45:39 -0700312
313 private MainThreadRequest sendRequestAsync(int command, int arg1) {
314 MainThreadRequest request = new MainThreadRequest();
315 mMainThreadHandler.obtainMessage(command, arg1, 0, request).sendToTarget();
316 return request;
317 }
318
319 /**
320 * Posts the specified command to be executed on the main thread, waits for the request to
321 * complete, and returns the result.
322 */
323 private Object sendRequest(int command) {
324 if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
325 throw new RuntimeException("This method will deadlock if called from the main thread.");
326 }
327
328 MainThreadRequest request = sendRequestAsync(command, 0);
329
330 // Wait for the request to complete
331 synchronized (request) {
332 while (request.result == null) {
333 try {
334 request.wait();
335 } catch (InterruptedException e) {
336 // Do nothing, go back and wait until the request is complete
337 }
338 }
339 }
340 return request.result;
341 }
Santos Cordonb64c1502014-05-21 21:21:49 -0700342}