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