Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1461bbce authored by Hui Wang's avatar Hui Wang Committed by Android (Google) Code Review
Browse files

Merge "Support Gba Api"

parents 0c5951d4 88634fd1
Loading
Loading
Loading
Loading
+527 −0
Original line number Diff line number Diff line
/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.telephony;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.telephony.IBootstrapAuthenticationCallback;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.gba.GbaAuthRequest;
import android.telephony.gba.GbaService;
import android.telephony.gba.IGbaService;
import android.text.TextUtils;
import android.util.SparseArray;

import com.android.internal.annotations.VisibleForTesting;
import com.android.telephony.Rlog;

import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * Class that serves as the layer between GbaService and ServiceStateTracker. It helps binding,
 * sending request, receiving callback, and registering for state change to GbaService.
 */
public class GbaManager {
    private static final boolean DBG = Build.IS_DEBUGGABLE;
    private static final int EVENT_BIND_SERVICE = 1;
    private static final int EVENT_UNBIND_SERVICE = 2;
    private static final int EVENT_BIND_FAIL = 3;
    private static final int EVENT_BIND_SUCCESS = 4;
    private static final int EVENT_CONFIG_CHANGED = 5;
    private static final int EVENT_REQUESTS_RECEIVED = 6;

    @VisibleForTesting
    public static final int RETRY_TIME_MS = 3000;
    @VisibleForTesting
    public static final int MAX_RETRY = 5;
    @VisibleForTesting
    public static final int REQUEST_TIMEOUT_MS = 5000;

    private final String mLogTag;
    private final Context mContext;
    private final int mSubId;

    private IGbaService mIGbaService;
    private GbaDeathRecipient mDeathRecipient;
    private String mTargetBindingPackageName;
    private GbaServiceConnection mServiceConnection;
    private Handler mHandler;

    private String mServicePackageName;
    private String mServicePackageNameOverride;
    private int mReleaseTime;
    private int mRetryTimes = 0;

    //the requests to be sent to the GBA service
    private final ConcurrentLinkedQueue<GbaAuthRequest> mRequestQueue =
            new ConcurrentLinkedQueue<>();
    //the callbacks of the pending requests which have been sent to the GBA service
    private final SparseArray<IBootstrapAuthenticationCallback> mCallbacks = new SparseArray<>();

    private static final SparseArray<GbaManager> sGbaManagers = new SparseArray<>();

    private final class GbaManagerHandler extends Handler {
        GbaManagerHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            logv("handle msg:" + msg.what);
            switch (msg.what) {
                case EVENT_BIND_SERVICE:
                    if (mRetryTimes++ < MAX_RETRY) {
                        rebindService(false);
                    } else {
                        loge("Too many retries, stop now!");
                        sendEmptyMessage(EVENT_BIND_FAIL);
                    }
                    break;
                case EVENT_UNBIND_SERVICE:
                    //do nothing if new requests are coming
                    if (mRequestQueue.isEmpty()) {
                        clearCallbacksAndNotifyFailure();
                        unbindService();
                    }
                    break;
                case EVENT_BIND_FAIL:
                case EVENT_BIND_SUCCESS:
                    mRetryTimes = 0;
                    processRequests();
                    break;
                case EVENT_REQUESTS_RECEIVED:
                    if (isServiceConnected()) {
                        processRequests();
                    } else {
                        if (!mHandler.hasMessages(EVENT_BIND_SERVICE)) {
                            mHandler.sendEmptyMessage(EVENT_BIND_SERVICE);
                        }
                    }
                    break;
                case EVENT_CONFIG_CHANGED:
                    mRetryTimes = 0;
                    if (isServiceConnetable() || isServiceConnected()) {
                        //force to rebind when config is changed
                        rebindService(true);
                    }
                    break;
                default:
                    loge("Unhandled event " + msg.what);
            }
        }
    }

    private final class GbaDeathRecipient implements IBinder.DeathRecipient {

        private final ComponentName mComponentName;
        private IBinder mBinder;

        GbaDeathRecipient(ComponentName name) {
            mComponentName = name;
        }

        public void linkToDeath(IBinder service) throws RemoteException {
            if (service != null) {
                mBinder = service;
                mBinder.linkToDeath(this, 0);
            }
        }

        public synchronized void unlinkToDeath() {
            if (mBinder != null) {
                mBinder.unlinkToDeath(this, 0);
                mBinder = null;
            }
        }

        @Override
        public void binderDied() {
            logd("GbaService(" + mComponentName + ") has died.");
            unlinkToDeath();
            //retry if died
            retryBind();
        }
    }

    private final class GbaServiceConnection implements ServiceConnection {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            logd("service " + name + " for Gba is connected.");
            mIGbaService = IGbaService.Stub.asInterface(service);
            mDeathRecipient = new GbaDeathRecipient(name);
            try {
                mDeathRecipient.linkToDeath(service);
            } catch (RemoteException exception) {
                // Remote exception means that the binder already died.
                mDeathRecipient.binderDied();
                logd("RemoteException " + exception);
            }
            mHandler.sendEmptyMessage(EVENT_BIND_SUCCESS);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            logd("service " + name + " is now disconnected.");
            mTargetBindingPackageName = null;
        }
    }

    @VisibleForTesting
    public GbaManager(Context context, int subId, String servicePackageName, int releaseTime) {
        mContext = context;
        mSubId = subId;
        mLogTag = "GbaManager[" + subId + "]";

        mServicePackageName = servicePackageName;
        mReleaseTime = releaseTime;

        HandlerThread headlerThread = new HandlerThread(mLogTag);
        headlerThread.start();
        mHandler = new GbaManagerHandler(headlerThread.getLooper());

        if (mReleaseTime < 0) {
            mHandler.sendEmptyMessage(EVENT_BIND_SERVICE);
        }
    }

    /**
     * create a GbaManager instance for a sub
     */
    public static GbaManager make(Context context, int subId,
            String servicePackageName, int releaseTime) {
        GbaManager gm = new GbaManager(context, subId, servicePackageName, releaseTime);
        synchronized (sGbaManagers) {
            sGbaManagers.put(subId, gm);
        }
        return gm;
    }

    /**
     * get singleton instance of GbaManager
     * @return GbaManager
     */
    public static GbaManager getInstance(int subId) {
        synchronized (sGbaManagers) {
            return sGbaManagers.get(subId);
        }
    }

    /**
     * handle the bootstrap authentication request
     * @hide
     */
    public void bootstrapAuthenticationRequest(GbaAuthRequest req) {
        logv("bootstrapAuthenticationRequest: " + req);
        //No GBA service configured
        if (TextUtils.isEmpty(getServicePackage())) {
            logd("do not support!");
            try {
                req.getCallback().onAuthenticationFailure(req.getToken(),
                        TelephonyManager.GBA_FAILURE_REASON_FEATURE_NOT_SUPPORTED);
            } catch (RemoteException exception) {
                loge("exception to call service: " + exception);
            }
            return;
        }

        mRequestQueue.offer(req);
        if (!mHandler.hasMessages(EVENT_REQUESTS_RECEIVED)) {
            mHandler.sendEmptyMessage(EVENT_REQUESTS_RECEIVED);
        }
    }

    private final IBootstrapAuthenticationCallback mServiceCallback =
            new IBootstrapAuthenticationCallback.Stub() {
                @Override
                public void onKeysAvailable(int token, byte[] gbaKey, String btId) {
                    logv("onKeysAvailable: " + Integer.toHexString(token) + ", id: " + btId);

                    IBootstrapAuthenticationCallback cb = null;
                    synchronized (mCallbacks) {
                        cb = mCallbacks.get(token);
                    }
                    if (cb != null) {
                        try {
                            cb.onKeysAvailable(token, gbaKey, btId);
                        } catch (RemoteException exception) {
                            logd("RemoteException " + exception);
                        }
                        synchronized (mCallbacks) {
                            mCallbacks.remove(token);
                            if (mCallbacks.size() == 0) {
                                releaseServiceAsNeeded(0);
                            }
                        }
                    }
                }

                @Override
                public void onAuthenticationFailure(int token, int reason) {
                    logd("onAuthenticationFailure: "
                            + Integer.toHexString(token) + " for: " + reason);

                    IBootstrapAuthenticationCallback cb = null;
                    synchronized (mCallbacks) {
                        cb = mCallbacks.get(token);
                    }
                    if (cb != null) {
                        try {
                            cb.onAuthenticationFailure(token, reason);
                        } catch (RemoteException exception) {
                            logd("RemoteException " + exception);
                        }
                        synchronized (mCallbacks) {
                            mCallbacks.remove(token);
                            if (mCallbacks.size() == 0) {
                                releaseServiceAsNeeded(0);
                            }
                        }
                    }
                }
            };

    private void processRequests() {
        if (isServiceConnected()) {
            try {
                while (!mRequestQueue.isEmpty()) {
                    GbaAuthRequest request = new GbaAuthRequest(mRequestQueue.peek());
                    synchronized (mCallbacks) {
                        mCallbacks.put(request.getToken(), request.getCallback());
                    }
                    request.setCallback(mServiceCallback);
                    mIGbaService.authenticationRequest(request);
                    mRequestQueue.poll();
                }
            } catch (RemoteException exception) {
                // Remote exception means that the binder already died.
                mDeathRecipient.binderDied();
                logd("RemoteException " + exception);
            }
        } else {
            while (!mRequestQueue.isEmpty()) {
                GbaAuthRequest req = mRequestQueue.poll();
                try {
                    req.getCallback().onAuthenticationFailure(req.getToken(),
                            TelephonyManager.GBA_FAILURE_REASON_FEATURE_NOT_SUPPORTED);
                } catch (RemoteException exception) {
                    logd("RemoteException " + exception);
                }
            }
        }

        releaseServiceAsNeeded(REQUEST_TIMEOUT_MS);
    }

    private void releaseServiceAsNeeded(int timeout) {
        int configReleaseTime = getReleaseTime();
        //always on
        if (configReleaseTime < 0) {
            return;
        }
        //schedule to release service
        int delayTime = configReleaseTime > timeout ? configReleaseTime : timeout;
        if (mHandler.hasMessages(EVENT_UNBIND_SERVICE)) {
            mHandler.removeMessages(EVENT_UNBIND_SERVICE);
        }
        mHandler.sendEmptyMessageDelayed(EVENT_UNBIND_SERVICE, delayTime);
    }

    private void clearCallbacksAndNotifyFailure() {
        synchronized (mCallbacks) {
            for (int i = 0; i < mCallbacks.size(); i++) {
                IBootstrapAuthenticationCallback cb = mCallbacks.valueAt(i);
                try {
                    cb.onAuthenticationFailure(mCallbacks.keyAt(i),
                            TelephonyManager.GBA_FAILURE_REASON_UNKNOWN);
                } catch (RemoteException exception) {
                    logd("RemoteException " + exception);
                }
            }
            mCallbacks.clear();
        }
    }

    /** return if GBA service has been connected */
    @VisibleForTesting
    public boolean isServiceConnected() {
        //current bound service should be the updated service package.
        synchronized (this) {
            return (mIGbaService != null) && (mIGbaService.asBinder().isBinderAlive())
                    && TextUtils.equals(mServicePackageName, mTargetBindingPackageName);
        }
    }

    private boolean isServiceConnetable() {
        synchronized (this) {
            return mTargetBindingPackageName != null || (
                    mReleaseTime < 0 && !TextUtils.isEmpty(mServicePackageName));
        }
    }

    private void unbindService() {
        if (mDeathRecipient != null) {
            mDeathRecipient.unlinkToDeath();
        }
        if (mServiceConnection != null) {
            logv("unbind service.");
            mContext.unbindService(mServiceConnection);
        }
        mDeathRecipient = null;
        mIGbaService = null;
        mServiceConnection = null;
        mTargetBindingPackageName = null;
    }

    private void bindService() {
        if (mContext == null || !SubscriptionManager.isValidSubscriptionId(mSubId)) {
            loge("Can't bind service with invalid sub Id.");
            return;
        }

        String servicePackage = getServicePackage();
        if (TextUtils.isEmpty(servicePackage)) {
            loge("Can't find the binding package");
            return;
        }

        Intent intent = new Intent(GbaService.SERVICE_INTERFACE);
        intent.setPackage(servicePackage);

        try {
            logv("Trying to bind " + servicePackage);
            mServiceConnection = new GbaServiceConnection();
            if (!mContext.bindService(intent, mServiceConnection,
                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE)) {
                logd("Cannot bind to the service.");
                retryBind();
                return;
            }
            mTargetBindingPackageName = servicePackage;
        } catch (SecurityException exception) {
            loge("bindService failed " + exception);
        }
    }

    private void retryBind() {
        //do nothing if binding service has been scheduled
        if (mHandler.hasMessages(EVENT_BIND_SERVICE)) {
            logv("wait for pending retry.");
            return;
        }

        logv("starting retry:" + mRetryTimes);

        mHandler.sendEmptyMessageDelayed(EVENT_BIND_SERVICE, RETRY_TIME_MS);
    }

    private void rebindService(boolean isForce) {
        // Do nothing if no need to rebind.
        if (!isForce && isServiceConnected()) {
            logv("Service " + getServicePackage() + " already bound or being bound.");
            return;
        }

        unbindService();
        bindService();
    }

    /** override GBA service package name to be connected */
    public boolean overrideServicePackage(String packageName) {
        synchronized (this) {
            if (!TextUtils.equals(mServicePackageName, packageName)) {
                logv("Service package name is changed from " + mServicePackageName
                        + " to " + packageName);
                mServicePackageName = packageName;
                if (!mHandler.hasMessages(EVENT_CONFIG_CHANGED)) {
                    mHandler.sendEmptyMessage(EVENT_CONFIG_CHANGED);
                }
                return true;
            }
        }
        return false;
    }

    /** return GBA service package name */
    public String getServicePackage() {
        synchronized (this) {
            return mServicePackageName;
        }
    }

    /** override the release time to unbind GBA service after the request is handled */
    public boolean overrideReleaseTime(int interval) {
        synchronized (this) {
            if (mReleaseTime != interval) {
                logv("Service release time is changed from " + mReleaseTime
                        + " to " + interval);
                mReleaseTime = interval;
                if (!mHandler.hasMessages(EVENT_CONFIG_CHANGED)) {
                    mHandler.sendEmptyMessage(EVENT_CONFIG_CHANGED);
                }
                return true;
            }
        }
        return false;
    }

    /** return the release time to unbind GBA service after the request is handled */
    public int getReleaseTime() {
        synchronized (this) {
            return mReleaseTime;
        }
    }

    @VisibleForTesting
    public Handler getHandler() {
        return mHandler;
    }

    /** only for testing */
    @VisibleForTesting
    public void destroy() {
        mHandler.removeCallbacksAndMessages(null);
        mHandler.getLooper().quit();
        mRequestQueue.clear();
        mCallbacks.clear();
        unbindService();
        sGbaManagers.remove(mSubId);
    }

    private void logv(String msg) {
        if (DBG) {
            Rlog.d(mLogTag, msg);
        }
    }

    private void logd(String msg) {
        Rlog.d(mLogTag, msg);
    }

    private void loge(String msg) {
        Rlog.e(mLogTag, msg);
    }
}
+259 −0

File added.

Preview size limit exceeded, changes collapsed.