1 /*
2  * Copyright 2020 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 
17 package com.android.internal.telephony;
18 
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.ServiceConnection;
23 import android.os.Build;
24 import android.os.Handler;
25 import android.os.HandlerThread;
26 import android.os.IBinder;
27 import android.os.Looper;
28 import android.os.Message;
29 import android.os.RemoteException;
30 import android.telephony.IBootstrapAuthenticationCallback;
31 import android.telephony.SubscriptionManager;
32 import android.telephony.TelephonyManager;
33 import android.telephony.gba.GbaAuthRequest;
34 import android.telephony.gba.GbaService;
35 import android.telephony.gba.IGbaService;
36 import android.text.TextUtils;
37 import android.util.SparseArray;
38 
39 import com.android.internal.annotations.VisibleForTesting;
40 import com.android.internal.telephony.metrics.RcsStats;
41 import com.android.telephony.Rlog;
42 
43 import java.util.concurrent.ConcurrentLinkedQueue;
44 
45 /**
46  * Class that serves as the layer between GbaService and ServiceStateTracker. It helps binding,
47  * sending request, receiving callback, and registering for state change to GbaService.
48  */
49 public class GbaManager {
50     private static final boolean DBG = Build.IS_DEBUGGABLE;
51     private static final int EVENT_BIND_SERVICE = 1;
52     private static final int EVENT_UNBIND_SERVICE = 2;
53     private static final int EVENT_BIND_FAIL = 3;
54     private static final int EVENT_BIND_SUCCESS = 4;
55     private static final int EVENT_CONFIG_CHANGED = 5;
56     private static final int EVENT_REQUESTS_RECEIVED = 6;
57 
58     @VisibleForTesting
59     public static final int RETRY_TIME_MS = 3000;
60     @VisibleForTesting
61     public static final int MAX_RETRY = 5;
62     @VisibleForTesting
63     public static final int REQUEST_TIMEOUT_MS = 5000;
64     private final RcsStats mRcsStats;
65 
66     private final String mLogTag;
67     private final Context mContext;
68     private final int mSubId;
69 
70     private IGbaService mIGbaService;
71     private GbaDeathRecipient mDeathRecipient;
72     private String mTargetBindingPackageName;
73     private GbaServiceConnection mServiceConnection;
74     private Handler mHandler;
75 
76     private String mServicePackageName;
77     private String mServicePackageNameOverride;
78     private int mReleaseTime;
79     private int mRetryTimes = 0;
80 
81     //the requests to be sent to the GBA service
82     private final ConcurrentLinkedQueue<GbaAuthRequest> mRequestQueue =
83             new ConcurrentLinkedQueue<>();
84     //the callbacks of the pending requests which have been sent to the GBA service
85     private final SparseArray<IBootstrapAuthenticationCallback> mCallbacks = new SparseArray<>();
86 
87     private static final SparseArray<GbaManager> sGbaManagers = new SparseArray<>();
88 
89     private final class GbaManagerHandler extends Handler {
GbaManagerHandler(Looper looper)90         GbaManagerHandler(Looper looper) {
91             super(looper);
92         }
93 
94         @Override
handleMessage(Message msg)95         public void handleMessage(Message msg) {
96             logv("handle msg:" + msg.what);
97             switch (msg.what) {
98                 case EVENT_BIND_SERVICE:
99                     if (mRetryTimes++ < MAX_RETRY) {
100                         rebindService(false);
101                     } else {
102                         loge("Too many retries, stop now!");
103                         sendEmptyMessage(EVENT_BIND_FAIL);
104                     }
105                     break;
106                 case EVENT_UNBIND_SERVICE:
107                     //do nothing if new requests are coming
108                     if (mRequestQueue.isEmpty()) {
109                         clearCallbacksAndNotifyFailure();
110                         unbindService();
111                     }
112                     break;
113                 case EVENT_BIND_FAIL:
114                 case EVENT_BIND_SUCCESS:
115                     mRetryTimes = 0;
116                     processRequests();
117                     break;
118                 case EVENT_REQUESTS_RECEIVED:
119                     if (isServiceConnected()) {
120                         processRequests();
121                     } else {
122                         if (!mHandler.hasMessages(EVENT_BIND_SERVICE)) {
123                             mHandler.sendEmptyMessage(EVENT_BIND_SERVICE);
124                         }
125                     }
126                     break;
127                 case EVENT_CONFIG_CHANGED:
128                     mRetryTimes = 0;
129                     if (isServiceConnetable() || isServiceConnected()) {
130                         //force to rebind when config is changed
131                         rebindService(true);
132                     }
133                     break;
134                 default:
135                     loge("Unhandled event " + msg.what);
136             }
137         }
138     }
139 
140     private final class GbaDeathRecipient implements IBinder.DeathRecipient {
141 
142         private final ComponentName mComponentName;
143         private IBinder mBinder;
144 
GbaDeathRecipient(ComponentName name)145         GbaDeathRecipient(ComponentName name) {
146             mComponentName = name;
147         }
148 
linkToDeath(IBinder service)149         public void linkToDeath(IBinder service) throws RemoteException {
150             if (service != null) {
151                 mBinder = service;
152                 mBinder.linkToDeath(this, 0);
153             }
154         }
155 
unlinkToDeath()156         public synchronized void unlinkToDeath() {
157             if (mBinder != null) {
158                 mBinder.unlinkToDeath(this, 0);
159                 mBinder = null;
160             }
161         }
162 
163         @Override
binderDied()164         public void binderDied() {
165             logd("GbaService(" + mComponentName + ") has died.");
166             unlinkToDeath();
167             //retry if died
168             retryBind();
169         }
170     }
171 
172     private final class GbaServiceConnection implements ServiceConnection {
173         @Override
onServiceConnected(ComponentName name, IBinder service)174         public void onServiceConnected(ComponentName name, IBinder service) {
175             logd("service " + name + " for Gba is connected.");
176             mIGbaService = IGbaService.Stub.asInterface(service);
177             mDeathRecipient = new GbaDeathRecipient(name);
178             try {
179                 mDeathRecipient.linkToDeath(service);
180             } catch (RemoteException exception) {
181                 // Remote exception means that the binder already died.
182                 mDeathRecipient.binderDied();
183                 logd("RemoteException " + exception);
184             }
185             mHandler.sendEmptyMessage(EVENT_BIND_SUCCESS);
186         }
187 
188         @Override
onServiceDisconnected(ComponentName name)189         public void onServiceDisconnected(ComponentName name) {
190             logd("service " + name + " is now disconnected.");
191             mTargetBindingPackageName = null;
192         }
193     }
194 
195     @VisibleForTesting
GbaManager(Context context, int subId, String servicePackageName, int releaseTime, RcsStats rcsStats)196     public GbaManager(Context context, int subId, String servicePackageName, int releaseTime,
197             RcsStats rcsStats) {
198         mContext = context;
199         mSubId = subId;
200         mLogTag = "GbaManager[" + subId + "]";
201 
202         mServicePackageName = servicePackageName;
203         mReleaseTime = releaseTime;
204 
205         HandlerThread headlerThread = new HandlerThread(mLogTag);
206         headlerThread.start();
207         mHandler = new GbaManagerHandler(headlerThread.getLooper());
208 
209         if (mReleaseTime < 0) {
210             mHandler.sendEmptyMessage(EVENT_BIND_SERVICE);
211         }
212         mRcsStats = rcsStats;
213     }
214 
215     /**
216      * create a GbaManager instance for a sub
217      */
make(Context context, int subId, String servicePackageName, int releaseTime)218     public static GbaManager make(Context context, int subId,
219             String servicePackageName, int releaseTime) {
220         GbaManager gm = new GbaManager(context, subId, servicePackageName, releaseTime,
221                 RcsStats.getInstance());
222         synchronized (sGbaManagers) {
223             sGbaManagers.put(subId, gm);
224         }
225         return gm;
226     }
227 
228     /**
229      * get singleton instance of GbaManager
230      * @return GbaManager
231      */
getInstance(int subId)232     public static GbaManager getInstance(int subId) {
233         synchronized (sGbaManagers) {
234             return sGbaManagers.get(subId);
235         }
236     }
237 
238     /**
239      * handle the bootstrap authentication request
240      * @hide
241      */
bootstrapAuthenticationRequest(GbaAuthRequest req)242     public void bootstrapAuthenticationRequest(GbaAuthRequest req) {
243         logv("bootstrapAuthenticationRequest: " + req);
244         //No GBA service configured
245         if (TextUtils.isEmpty(getServicePackage())) {
246             logd("do not support!");
247             try {
248                 req.getCallback().onAuthenticationFailure(req.getToken(),
249                         TelephonyManager.GBA_FAILURE_REASON_FEATURE_NOT_SUPPORTED);
250             } catch (RemoteException exception) {
251                 loge("exception to call service: " + exception);
252             }
253             return;
254         }
255 
256         mRequestQueue.offer(req);
257         if (!mHandler.hasMessages(EVENT_REQUESTS_RECEIVED)) {
258             mHandler.sendEmptyMessage(EVENT_REQUESTS_RECEIVED);
259         }
260     }
261 
262     private final IBootstrapAuthenticationCallback mServiceCallback =
263             new IBootstrapAuthenticationCallback.Stub() {
264                 @Override
265                 public void onKeysAvailable(int token, byte[] gbaKey, String btId) {
266                     logv("onKeysAvailable: " + Integer.toHexString(token) + ", id: " + btId);
267 
268                     IBootstrapAuthenticationCallback cb = null;
269                     synchronized (mCallbacks) {
270                         cb = mCallbacks.get(token);
271                     }
272                     if (cb != null) {
273                         try {
274                             cb.onKeysAvailable(token, gbaKey, btId);
275                             mRcsStats.onGbaSuccessEvent(mSubId);
276                         } catch (RemoteException exception) {
277                             logd("RemoteException " + exception);
278                         }
279                         synchronized (mCallbacks) {
280                             mCallbacks.remove(token);
281                             if (mCallbacks.size() == 0) {
282                                 releaseServiceAsNeeded(0);
283                             }
284                         }
285                     }
286                 }
287 
288                 @Override
289                 public void onAuthenticationFailure(int token, int reason) {
290                     logd("onAuthenticationFailure: "
291                             + Integer.toHexString(token) + " for: " + reason);
292 
293                     IBootstrapAuthenticationCallback cb = null;
294                     synchronized (mCallbacks) {
295                         cb = mCallbacks.get(token);
296                     }
297                     if (cb != null) {
298                         try {
299                             cb.onAuthenticationFailure(token, reason);
300                             mRcsStats.onGbaFailureEvent(mSubId, reason);
301                         } catch (RemoteException exception) {
302                             logd("RemoteException " + exception);
303                         }
304                         synchronized (mCallbacks) {
305                             mCallbacks.remove(token);
306                             if (mCallbacks.size() == 0) {
307                                 releaseServiceAsNeeded(0);
308                             }
309                         }
310                     }
311                 }
312             };
313 
processRequests()314     private void processRequests() {
315         if (isServiceConnected()) {
316             try {
317                 while (!mRequestQueue.isEmpty()) {
318                     GbaAuthRequest request = new GbaAuthRequest(mRequestQueue.peek());
319                     synchronized (mCallbacks) {
320                         mCallbacks.put(request.getToken(), request.getCallback());
321                     }
322                     request.setCallback(mServiceCallback);
323                     mIGbaService.authenticationRequest(request);
324                     mRequestQueue.poll();
325                 }
326             } catch (RemoteException exception) {
327                 // Remote exception means that the binder already died.
328                 mDeathRecipient.binderDied();
329                 logd("RemoteException " + exception);
330             }
331         } else {
332             while (!mRequestQueue.isEmpty()) {
333                 GbaAuthRequest req = mRequestQueue.poll();
334                 try {
335                     req.getCallback().onAuthenticationFailure(req.getToken(),
336                             TelephonyManager.GBA_FAILURE_REASON_FEATURE_NOT_SUPPORTED);
337                 } catch (RemoteException exception) {
338                     logd("RemoteException " + exception);
339                 }
340             }
341         }
342 
343         releaseServiceAsNeeded(REQUEST_TIMEOUT_MS);
344     }
345 
releaseServiceAsNeeded(int timeout)346     private void releaseServiceAsNeeded(int timeout) {
347         int configReleaseTime = getReleaseTime();
348         //always on
349         if (configReleaseTime < 0) {
350             return;
351         }
352         //schedule to release service
353         int delayTime = configReleaseTime > timeout ? configReleaseTime : timeout;
354         if (mHandler.hasMessages(EVENT_UNBIND_SERVICE)) {
355             mHandler.removeMessages(EVENT_UNBIND_SERVICE);
356         }
357         mHandler.sendEmptyMessageDelayed(EVENT_UNBIND_SERVICE, delayTime);
358     }
359 
clearCallbacksAndNotifyFailure()360     private void clearCallbacksAndNotifyFailure() {
361         synchronized (mCallbacks) {
362             for (int i = 0; i < mCallbacks.size(); i++) {
363                 IBootstrapAuthenticationCallback cb = mCallbacks.valueAt(i);
364                 try {
365                     cb.onAuthenticationFailure(mCallbacks.keyAt(i),
366                             TelephonyManager.GBA_FAILURE_REASON_UNKNOWN);
367                 } catch (RemoteException exception) {
368                     logd("RemoteException " + exception);
369                 }
370             }
371             mCallbacks.clear();
372         }
373     }
374 
375     /** return if GBA service has been connected */
376     @VisibleForTesting
isServiceConnected()377     public boolean isServiceConnected() {
378         //current bound service should be the updated service package.
379         synchronized (this) {
380             return (mIGbaService != null) && (mIGbaService.asBinder().isBinderAlive())
381                     && TextUtils.equals(mServicePackageName, mTargetBindingPackageName);
382         }
383     }
384 
isServiceConnetable()385     private boolean isServiceConnetable() {
386         synchronized (this) {
387             return mTargetBindingPackageName != null || (
388                     mReleaseTime < 0 && !TextUtils.isEmpty(mServicePackageName));
389         }
390     }
391 
unbindService()392     private void unbindService() {
393         if (mDeathRecipient != null) {
394             mDeathRecipient.unlinkToDeath();
395         }
396         if (mServiceConnection != null) {
397             logv("unbind service.");
398             mContext.unbindService(mServiceConnection);
399         }
400         mDeathRecipient = null;
401         mIGbaService = null;
402         mServiceConnection = null;
403         mTargetBindingPackageName = null;
404     }
405 
bindService()406     private void bindService() {
407         if (mContext == null || !SubscriptionManager.isValidSubscriptionId(mSubId)) {
408             loge("Can't bind service with invalid sub Id.");
409             return;
410         }
411 
412         String servicePackage = getServicePackage();
413         if (TextUtils.isEmpty(servicePackage)) {
414             loge("Can't find the binding package");
415             return;
416         }
417 
418         Intent intent = new Intent(GbaService.SERVICE_INTERFACE);
419         intent.setPackage(servicePackage);
420 
421         try {
422             logv("Trying to bind " + servicePackage);
423             mServiceConnection = new GbaServiceConnection();
424             if (!mContext.bindService(intent, mServiceConnection,
425                     Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE)) {
426                 logd("Cannot bind to the service.");
427                 retryBind();
428                 return;
429             }
430             mTargetBindingPackageName = servicePackage;
431         } catch (SecurityException exception) {
432             loge("bindService failed " + exception);
433         }
434     }
435 
retryBind()436     private void retryBind() {
437         //do nothing if binding service has been scheduled
438         if (mHandler.hasMessages(EVENT_BIND_SERVICE)) {
439             logv("wait for pending retry.");
440             return;
441         }
442 
443         logv("starting retry:" + mRetryTimes);
444 
445         mHandler.sendEmptyMessageDelayed(EVENT_BIND_SERVICE, RETRY_TIME_MS);
446     }
447 
rebindService(boolean isForce)448     private void rebindService(boolean isForce) {
449         // Do nothing if no need to rebind.
450         if (!isForce && isServiceConnected()) {
451             logv("Service " + getServicePackage() + " already bound or being bound.");
452             return;
453         }
454 
455         unbindService();
456         bindService();
457     }
458 
459     /** override GBA service package name to be connected */
overrideServicePackage(String packageName)460     public boolean overrideServicePackage(String packageName) {
461         synchronized (this) {
462             if (!TextUtils.equals(mServicePackageName, packageName)) {
463                 logv("Service package name is changed from " + mServicePackageName
464                         + " to " + packageName);
465                 mServicePackageName = packageName;
466                 if (!mHandler.hasMessages(EVENT_CONFIG_CHANGED)) {
467                     mHandler.sendEmptyMessage(EVENT_CONFIG_CHANGED);
468                 }
469                 return true;
470             }
471         }
472         return false;
473     }
474 
475     /** return GBA service package name */
getServicePackage()476     public String getServicePackage() {
477         synchronized (this) {
478             return mServicePackageName;
479         }
480     }
481 
482     /** override the release time to unbind GBA service after the request is handled */
overrideReleaseTime(int interval)483     public boolean overrideReleaseTime(int interval) {
484         synchronized (this) {
485             if (mReleaseTime != interval) {
486                 logv("Service release time is changed from " + mReleaseTime
487                         + " to " + interval);
488                 mReleaseTime = interval;
489                 if (!mHandler.hasMessages(EVENT_CONFIG_CHANGED)) {
490                     mHandler.sendEmptyMessage(EVENT_CONFIG_CHANGED);
491                 }
492                 return true;
493             }
494         }
495         return false;
496     }
497 
498     /** return the release time to unbind GBA service after the request is handled */
getReleaseTime()499     public int getReleaseTime() {
500         synchronized (this) {
501             return mReleaseTime;
502         }
503     }
504 
505     @VisibleForTesting
getHandler()506     public Handler getHandler() {
507         return mHandler;
508     }
509 
510     /** only for testing */
511     @VisibleForTesting
destroy()512     public void destroy() {
513         mHandler.removeCallbacksAndMessages(null);
514         mHandler.getLooper().quit();
515         mRequestQueue.clear();
516         mCallbacks.clear();
517         unbindService();
518         sGbaManagers.remove(mSubId);
519     }
520 
logv(String msg)521     private void logv(String msg) {
522         if (DBG) {
523             Rlog.d(mLogTag, msg);
524         }
525     }
526 
logd(String msg)527     private void logd(String msg) {
528         Rlog.d(mLogTag, msg);
529     }
530 
loge(String msg)531     private void loge(String msg) {
532         Rlog.e(mLogTag, msg);
533     }
534 }
535