CSipSimple源码分析(一)之代码流程和用户注册流程

来源:互联网 发布:coc野蛮人之王升级数据 编辑:程序博客网 时间:2024/05/16 13:59

简介

CSipSimple是Android手机上支持Sip的网络电话软件 , 基于的是pjsip开源sip协议栈

需要了解pjsip , 可以下载PJSUA开发文档中文版看看

源码流程

代码的大致流程如下 :

        中间肯定有SipService/PjsioService和DBProvider的交互                        UI        ===================================                ^               |                |               |                |               V              Intent        SipService            DBProvider          |                ^               |                |               V                |           PjsipService          UAStateReciver        |                ^               |                |               V        ===================================                     pjsip底层库

注册流程分析

 UI界面 :     public class BasePrefsWizard extends GenericPrefs{        /**         * 保存账户并将账户保存或更新到数据库         * @param wizardId         */        private void saveAccount(String wizardId) {//BASIC            boolean needRestart = false;            PreferencesWrapper prefs = new PreferencesWrapper(getApplicationContext());            account = wizard.buildAccount(account);//1 . 创建账户 , 策略设计模式            account.wizard = wizardId;            if (account.id == SipProfile.INVALID_ID) {//2 . 创建新的账户时为true                // This account does not exists yet                prefs.startEditing();                wizard.setDefaultParams(prefs);                prefs.endEditing();                applyNewAccountDefault(account);                //3 . 重点 : 将用户信息插入到数据库                Uri uri = getContentResolver().insert(SipProfile.ACCOUNT_URI, account.getDbContentValues());                // After insert, add filters for this wizard                 account.id = ContentUris.parseId(uri);                List<Filter> filters = wizard.getDefaultFilters(account);//null                if (filters != null) {                    for (Filter filter : filters) { //4 . 如果在设置模块的过滤器进行过配置 , 会执行以下代码                        // Ensure the correct id if not done by the wizard                        filter.account = (int) account.id;                        getContentResolver().insert(SipManager.FILTER_URI, filter.getDbContentValues());                    }                }                // Check if we have to restart                needRestart = wizard.needRestart();            } else {//2 . 更新账户信息时执行                // TODO : should not be done there but if not we should add an                // option to re-apply default params                prefs.startEditing();                wizard.setDefaultParams(prefs);                prefs.endEditing();                //3 . 重点 : 将账户信息更新到数据库中                getContentResolver().update(ContentUris.withAppendedId(SipProfile.ACCOUNT_ID_URI_BASE, account.id), account.getDbContentValues(), null, null);            }            // Mainly if global preferences were changed, we have to restart sip stack             if (needRestart) {                 //5 . 在创建账户或更新账户时 , 修改过一些设置 , 会执行以下代码 , 进行重启协议栈                // 协议栈会更新配置信息                Intent intent = new Intent(SipManager.ACTION_SIP_REQUEST_RESTART);                sendBroadcast(intent);            }        }    }    /**     * 在上面的[1 . 创建账户]中 , 会使用该类     * 因为在选择向导的时候选择的是Basic  , 因此这里选择Basic类进行分析     */    public class Basic extends BaseImplementation {        /**         * 创建一个账户         */        public SipProfile buildAccount(SipProfile account) {            Log.d(THIS_FILE, "begin of save ....");            account.display_name = accountDisplayName.getText().trim();//用户名            String[] serverParts = accountServer.getText().split(":");//服务器            account.acc_id = "<sip:" + SipUri.encodeUser(accountUserName.getText().trim()) + "@"+serverParts[0].trim()+">";            String regUri = "sip:" + accountServer.getText();//            account.reg_uri = regUri;            account.proxies = new String[] { regUri } ;            account.realm = "*";            account.username = getText(accountUserName).trim();            account.data = getText(accountPassword);            account.scheme = SipProfile.CRED_SCHEME_DIGEST;            account.datatype = SipProfile.CRED_DATA_PLAIN_PASSWD;            //By default auto transport            account.transport = SipProfile.TRANSPORT_UDP;            return account;        }    }    /**     * 在上面[3 .  重点 : 将用户信息插入到数据库] 和      * [3 . 重点 : 将账户信息更新到数据库中]可以知道 ,     * 最终的用户信息是保存到数据库中的 , 因此下面分析数据库     */    public class DBProvider extends ContentProvider {        /**         * 将用户信息插入到数据库         */        @Override        public Uri insert(Uri uri, ContentValues initialValues){            ... //省略 代码太多 , 看下重点代码            case ACCOUNTS_STATUS_ID:                long id = ContentUris.parseId(uri);                synchronized (profilesStatus) {                    SipProfileState ps = new SipProfileState();                    if (profilesStatus.containsKey(id)) {                        ContentValues currentValues = profilesStatus.get(id);                        ps.createFromContentValue(currentValues);                    }                    ps.createFromContentValue(initialValues);                    ContentValues cv = ps.getAsContentValue();                    cv.put(SipProfileState.ACCOUNT_ID, id);                    profilesStatus.put(id, cv);                    Log.d(THIS_FILE, "Added " + cv);                }                // 1. 通知内容观察者有新的账户注册                getContext().getContentResolver().notifyChange(uri, null);            ...// 省略            if (rowId >= 0) {                // TODO : for inserted account register it here                // 2. 重点 , 通知内容观察者有新的账户注册 , 并通过Uri携带注册账户的ID                 // 后续可以通过该ID值 , 获取账户的状态信息等                Uri retUri = ContentUris.withAppendedId(baseInsertedUri, rowId);                getContext().getContentResolver().notifyChange(retUri, null);                if (matched == ACCOUNTS || matched == ACCOUNTS_ID) {                    broadcastAccountChange(rowId);// 3 . 重点通过广播通知账户信息发生改变                }                ... //省略            }        }        /**         * 更新用户数据         */        @Override        public int update(Uri uri, ContentValues values, String where,String[] whereArgs) {             ...// 省略            // 1 . 通知内容观察者有用户的信息发生改变            getContext().getContentResolver().notifyChange(uri, null);            long rowId = -1;            if (matched == ACCOUNTS_ID || matched == ACCOUNTS_STATUS_ID) {                rowId = ContentUris.parseId(uri); // 2. 获取账户的ID            }            if (rowId >= 0) {                if (matched == ACCOUNTS_ID) {                    // Don't broadcast if we only changed wizard or only changed                    // priority                    boolean doBroadcast = true;                    if (values.size() == 1) {                        if (values.containsKey(SipProfile.FIELD_WIZARD)) {                            doBroadcast = false;                        } else if (values.containsKey(SipProfile.FIELD_PRIORITY)) {                            doBroadcast = false;                        }                    }                    if (doBroadcast) {                        broadcastAccountChange(rowId);// 2. 重点 通过广播通知账户信息发生改变                    }                } else if (matched == ACCOUNTS_STATUS_ID) {                    broadcastRegistrationChange(rowId);                }                ... // 省略             }           }        }           /**         * 通过广播通知账户信息发生改变         */         private void broadcastAccountChange(long accountId) {             Intent publishIntent = new Intent(SipManager.ACTION_SIP_ACCOUNT_CHANGED);// 1. 重点 广播的动作             publishIntent.putExtra(SipProfile.FIELD_ID, accountId);             getContext().sendBroadcast(publishIntent);             BackupWrapper.getInstance(getContext()).dataChanged();         }      }    /**     * 该内容观察者用来接收上面[1. 重点 广播的动作] 的广播 ,     * 该内容观察者的注册是在SipService类中的registerBroadcasts()方法中进行的     */    public class DynamicReceiver4 extends BroadcastReceiver {         /**          * 处理账户信息发生改变的广播          */         private void onReceiveInternal(Context context, Intent intent, boolean isSticky) throws SameThreadException {            ... // 省略             else if (action.equals(SipManager.ACTION_SIP_ACCOUNT_CHANGED)) { // 1. 处理用户信息发生改变的广播                final long accountId = intent.getLongExtra(SipProfile.FIELD_ID, SipProfile.INVALID_ID);                // Should that be threaded?                if (accountId != SipProfile.INVALID_ID) {                    // 2. 通过广播携带的账户ID , 获取到账户的实例                    final SipProfile account = service.getAccount(accountId);                    if (account != null) {                        Log.d(THIS_FILE, "Enqueue set account registration");                        // 3 . 重点 进行账户的注册 , 该service就是SipService                        service.setAccountRegistration(account, account.active ? 1 : 0, true);                    }                }            }             ... // 省略         }    }    /**     * 重点 所有的有关通话/信息的操作都是通过该服务来实现的     */    public class SipService extends Service {        /**         * 注册账户的方法         */         public boolean setAccountRegistration(SipProfile account, int renew,            boolean forceReAdd) throws SameThreadException {            boolean status = false;            if (pjService != null) {                //1. 重点 实际注册账户是通过PjsipService来实现的                status = pjService.setAccountRegistration(account, renew,forceReAdd);            }            return status;        }    }    /**     * 重点 , 所有与pjsip库进行操作的方法 , 都是通过该类调用pjsua的native方法类实现的     */    public class PjSipService {        /**         * 进行账户向服务端注册的方法         */        public boolean setAccountRegistration(SipProfile account, int renew,            boolean forceReAdd) throws SameThreadException {            int status = -1;            if (!created || account == null) {                Log.e(THIS_FILE, "PJSIP is not started here, nothing can be done");                return false;            }            if (account.id == SipProfile.INVALID_ID) {                Log.w(THIS_FILE, "Trying to set registration on a deleted account");                return false;            }            // 获取账户的状态信息            SipProfileState profileState = getProfileState(account);            // If local account -- Ensure we are not deleting, because this would be            // invalid            if (profileState.getWizard().equalsIgnoreCase(                    WizardUtils.LOCAL_WIZARD_TAG)) {                if (renew == 0) {                    return false;                }            }            // In case of already added, we have to act finely            // If it's local we can just consider that we have to re-add account            // since it will actually just touch the account with a modify            if (profileState != null && profileState.isAddedToStack() && !profileState.getWizard().equalsIgnoreCase(WizardUtils.LOCAL_WIZARD_TAG)) {                // The account is already there in accounts list                service.getContentResolver().delete(ContentUris.withAppendedId(SipProfile.ACCOUNT_STATUS_URI,                                account.id), null, null);                Log.d(THIS_FILE,"Account already added to stack, remove and re-load or delete");                if (renew == 1) {                    if (forceReAdd) {// 先删除账户 , 然后重新进行添加账户                        status = pjsua.acc_del(profileState.getPjsuaId());                        addAccount(account); // 1. 重点 添加账户                    } else {                        // 设置账户的状态为在线 , 并重新进行注册                        pjsua.acc_set_online_status(profileState.getPjsuaId(),getOnlineForStatus(service.getPresence()));                        status = pjsua.acc_set_registration(profileState.getPjsuaId(), renew);                    }                } else {                    // if(status == pjsuaConstants.PJ_SUCCESS && renew == 0) {                    Log.d(THIS_FILE, "Delete account !!");                    status = pjsua.acc_del(profileState.getPjsuaId());// 删除账户                }            } else {                if (renew == 1) {                    addAccount(account); // 2 .重点 添加账户                } else {                    Log.w(THIS_FILE, "Ask to unregister an unexisting account !!" + account.id);                }            }            // PJ_SUCCESS = 0            return status == 0;        }        /**         * 重点 , 添加账户         */        public boolean addAccount(SipProfile profile) throws SameThreadException {            int status = pjsuaConstants.PJ_FALSE;            if (!created) {                Log.e(THIS_FILE, "PJSIP is not started here, nothing can be done");                return status == pjsuaConstants.PJ_SUCCESS;            }            PjSipAccount account = new PjSipAccount(profile);// 包装成PjSip账户            account.applyExtraParams(service);// 设置额外的信息            //账户状态实例 , 包含账户是否登录成功的状态            SipProfileState currentAccountStatus = getProfileState(profile);            account.cfg.setRegister_on_acc_add(pjsuaConstants.PJ_FALSE);            if (currentAccountStatus.isAddedToStack()) {// 判断该账户是否曾经加载过                pjsua.csipsimple_set_acc_user_data(// 曾经加载过                        currentAccountStatus.getPjsuaId(), account.css_cfg);                status = pjsua.acc_modify(currentAccountStatus.getPjsuaId(),                        account.cfg);// 1. 重点 , 对账户的配置进行修改 ,并返回该账户的状态                beforeAccountRegistration(currentAccountStatus.getPjsuaId(),                        profile);// 先清除该账户在代理服务器的状态                ContentValues cv = new ContentValues();                cv.put(SipProfileState.ADDED_STATUS, status);// 2. 重点 , 设置该账户的状态                //3 . 重点 , 讲该账户的状态更新到数据库中                service.getContentResolver().update(ContentUris.withAppendedId(                                SipProfile.ACCOUNT_STATUS_ID_URI_BASE, profile.id),cv, null, null);                // 判断通用向导的类型 , 如果不是LOCAL ,则继续向下执行 , 我们选择的是BASIC                if (!account.wizard.equalsIgnoreCase(WizardUtils.LOCAL_WIZARD_TAG)) {                    // Re register //重新进行注册                    if (status == pjsuaConstants.PJ_SUCCESS) {// 账户的配置修改成功                        status = pjsua.acc_set_registration(                                currentAccountStatus.getPjsuaId(), 1);// 则对账户设置注册                        if (status == pjsuaConstants.PJ_SUCCESS) {// 账户设置注册成功                            pjsua.acc_set_online_status(                                    currentAccountStatus.getPjsuaId(), 1);// 则设置该账户为在线状态                        }                    }                }            } else {// 如果该账户没有注册过                int[] accId = new int[1];                if (account.wizard.equalsIgnoreCase(WizardUtils.LOCAL_WIZARD_TAG)) {// 设置向导选择的BASIC , 不是LOCAL , 不向下执行                    // We already have local account by default                    // For now consider we are talking about UDP one                    // In the future local account should be set per transport                    switch (account.transport) {                    case SipProfile.TRANSPORT_UDP:                        accId[0] = prefsWrapper.useIPv6() ? localUdp6AccPjId                                : localUdpAccPjId;                        break;                    case SipProfile.TRANSPORT_TCP:                        accId[0] = prefsWrapper.useIPv6() ? localTcp6AccPjId                                : localTcpAccPjId;                        break;                    case SipProfile.TRANSPORT_TLS:                        accId[0] = prefsWrapper.useIPv6() ? localTls6AccPjId                                : localTlsAccPjId;                        break;                    default:                        // By default use UDP                        accId[0] = localUdpAccPjId;                        break;                    }                    pjsua.csipsimple_set_acc_user_data(accId[0], account.css_cfg);                    // TODO : use video cfg here                    // nCfg.setVid_in_auto_show(pjsuaConstants.PJ_TRUE);                    // nCfg.setVid_out_auto_transmit(pjsuaConstants.PJ_TRUE);                    // status = pjsua.acc_modify(accId[0], nCfg);                } else {                    // TODO 执行此处的代码                    // Cause of standard account different from local account :)                    status = pjsua.acc_add(account.cfg, pjsuaConstants.PJ_FALSE,                            accId);// 4. 进行账户的添加                    pjsua.csipsimple_set_acc_user_data(accId[0], account.css_cfg);// 设置账户数据                    beforeAccountRegistration(accId[0], profile);// 在对用户进行注册之前的操作,清除该账户的状态                    pjsua.acc_set_registration(accId[0], 1);//重点 4. 对账户进行注册                }                if (status == pjsuaConstants.PJ_SUCCESS) {//5. 账户注册成功                    SipProfileState ps = new SipProfileState(profile);// 获取该账户的Sip配置信息                    ps.setAddedStatus(status);// 6. 设置注册的状态                    ps.setPjsuaId(accId[0]);                    service.getContentResolver().insert(                            ContentUris.withAppendedId(                                    SipProfile.ACCOUNT_STATUS_ID_URI_BASE,                                    account.id), ps.getAsContentValue());// 7 .注册成功,并通知                    pjsua.acc_set_online_status(accId[0], 1);// 设置该账户为在线状态                }            }            return status == pjsuaConstants.PJ_SUCCESS;        }    }    /**     * UAStateReceiver , 该类重点 , 所有与pjsip交互的回调 , 都是通过此类完成的 ,     * 相关的api可以参考文档中的pjsua_callback , 这里只关注注册的回调     */    public class UAStateReceiver{            @Override            public void on_reg_state(final int accountId) {                lockCpu();                pjService.service.getExecutor().execute(new SipRunnable() {                    @Override                    public void doRun() throws SameThreadException {                        // Update java infos                        // 1 . 更新账户的状态 , 又回到PjsipService中                        pjService.updateProfileStateFromService(accountId);                    }                });                unlockCpu();            }    }    /**     * 重点 , 所有与pjsip库进行操作的方法 , 都是通过该类调用pjsua的native方法类实现的     */    public class PjsipService{            /**             * Synchronize content provider backend from pjsip stack             *              * @param pjsuaId the pjsua id of the account to synchronize             * @throws SameThreadException             */            public void updateProfileStateFromService(int pjsuaId) throws SameThreadException {                if (!created) {                    return;                }                long accId = getAccountIdForPjsipId(service, pjsuaId);// 1. 获取注册的账号                Log.d(THIS_FILE, "Update profile from service for " + pjsuaId + " aka in db " + accId);                if (accId != SipProfile.INVALID_ID) { //2. 判断accId                    int success = pjsuaConstants.PJ_FALSE;                    pjsua_acc_info pjAccountInfo;                    pjAccountInfo = new pjsua_acc_info();                    success = pjsua.acc_get_info(pjsuaId, pjAccountInfo); // 3. 重点 , 从pjsip协议栈中获取账户信息                    if (success == pjsuaConstants.PJ_SUCCESS && pjAccountInfo != null) {                        ContentValues cv = new ContentValues();                        // 4 . 设置用户的状态                        try {                            // Should be fine : status code are coherent with RFC                            // status codes                            cv.put(SipProfileState.STATUS_CODE, pjAccountInfo.getStatus().swigValue());                        } catch (IllegalArgumentException e) {                            cv.put(SipProfileState.STATUS_CODE,                                    SipCallSession.StatusCode.INTERNAL_SERVER_ERROR);                        }                        cv.put(SipProfileState.STATUS_TEXT, pjStrToString(pjAccountInfo.getStatus_text()));                        cv.put(SipProfileState.EXPIRES, pjAccountInfo.getExpires());                        //5 . 更新用户的状态                         service.getContentResolver().update(                                ContentUris.withAppendedId(SipProfile.ACCOUNT_STATUS_ID_URI_BASE, accId),                                cv, null, null);                        Log.d(THIS_FILE, "Profile state UP : " + cv);                    }                } else {                    Log.e(THIS_FILE, "Trying to update not added account " + pjsuaId);                }            }        /**         * 1. 获取注册的账号         */         public static long getAccountIdForPjsipId(Context ctxt, int pjId) {            long accId = SipProfile.INVALID_ID; //1 .设置accId            // 2 . 查找账户            Cursor c = ctxt.getContentResolver().query(SipProfile.ACCOUNT_STATUS_URI, null, null,                    null, null);            if (c != null) {                try {                    c.moveToFirst();                    do {                        int pjsuaId = c.getInt(c.getColumnIndex(SipProfileState.PJSUA_ID));                        Log.d(THIS_FILE, "Found pjsua " + pjsuaId + " searching " + pjId);                        if (pjsuaId == pjId) {                            accId = c.getInt(c.getColumnIndex(SipProfileState.ACCOUNT_ID));                            break;                        }                    } while (c.moveToNext());                } catch (Exception e) {                    Log.e(THIS_FILE, "Error on looping over sip profiles", e);                } finally {                    c.close();                }            }            return accId;        }    }    /**     * 最后又回到DBProvider , 其实所有账户UI层面的状态 , 都是和DBProvider打交道 ,     * MVC模式     */     public class DBProvider{            @Override            public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {                SQLiteDatabase db = mOpenHelper.getWritableDatabase();                int count;                String finalWhere;                int matched = URI_MATCHER.match(uri);                List<String> possibles = getPossibleFieldsForType(matched);                checkSelection(possibles, where);                // 1. 进行账户的更新 , 到此用户的注册创建注册流程已经走完                 // 接下来 , 我们看UI层面是怎么更新用户的状态的                switch (matched) {                    case ACCOUNTS:                        count = db.update(SipProfile.ACCOUNTS_TABLE_NAME, values, where, whereArgs);                        break;                    case ACCOUNTS_ID:                        finalWhere = DatabaseUtilsCompat.concatenateWhere(SipProfile.FIELD_ID + " = " + ContentUris.parseId(uri), where);                        count = db.update(SipProfile.ACCOUNTS_TABLE_NAME, values, finalWhere, whereArgs);                        break;                    // ... 只看和账户相关的更新                                       case ACCOUNTS_STATUS_ID:                        long id = ContentUris.parseId(uri);                        synchronized (profilesStatus){                            SipProfileState ps = new SipProfileState();                            if(profilesStatus.containsKey(id)) {                                ContentValues currentValues = profilesStatus.get(id);                                ps.createFromContentValue(currentValues);                            }                            ps.createFromContentValue(values);                            ContentValues cv = ps.getAsContentValue();                            cv.put(SipProfileState.ACCOUNT_ID, id);                            profilesStatus.put(id, cv);                            Log.d(THIS_FILE, "Updated "+cv);                        }                        count = 1;                        break;                    default:                        throw new IllegalArgumentException(UNKNOWN_URI_LOG + uri);                }                //内容观察者 , 提示数据库发生改变                getContext().getContentResolver().notifyChange(uri, null);                 long rowId = -1;                if (matched == ACCOUNTS_ID || matched == ACCOUNTS_STATUS_ID) {                    rowId = ContentUris.parseId(uri); //获取更新账户的id                }                if (rowId >= 0) {                    if (matched == ACCOUNTS_ID) {                        // Don't broadcast if we only changed wizard or only changed priority                        boolean doBroadcast = true;                        if(values.size() == 1) {                            if(values.containsKey(SipProfile.FIELD_WIZARD)) {                                doBroadcast = false;                            }else if(values.containsKey(SipProfile.FIELD_PRIORITY)) {                                doBroadcast = false;                            }                        }                        if(doBroadcast) {                            broadcastAccountChange(rowId); // 发送广播 , 告知用户信息发生改变                        }                    } else if (matched == ACCOUNTS_STATUS_ID) {                        broadcastRegistrationChange(rowId); // 发送广播 , 告知用户注册信息发生改变 , 主要更新AppWidget                    }                }                return count;            }    }    /**     * 刚才说过 , UI账户状态的操作 , 都是和数据层面打交道 ,     * 因此 , 这里用的是ContentResolver内容观察者     */    public class AccountsEditListFragment{        @Override        public void onResume() {            super.onResume();            if(statusObserver == null) {                statusObserver = new AccountStatusContentObserver(mHandler);// 1 . 账户状态观察者                getActivity().getContentResolver().registerContentObserver(SipProfile.ACCOUNT_STATUS_URI, true, statusObserver);            }            mAdapter.notifyDataSetChanged();        }        class AccountStatusContentObserver extends ContentObserver {            public AccountStatusContentObserver(Handler h) {                super(h);            }            public void onChange(boolean selfChange) {                Log.d(THIS_FILE, "Accounts status.onChange( " + selfChange + ")");                ((BaseAdapter) getListAdapter()).notifyDataSetChanged();// 2. 更新账户状态UI            }        }    }    /**     * 更新UI     */    public class AccountsEditListAdapter{        @Override        public void bindView(View view, Context context, Cursor cursor) {            super.bindView(view, context, cursor);            AccountListItemViews tagView = (AccountListItemViews) view.getTag();            if (tagView == null) {                tagView = tagRowView(view);            }            // Get the view object and account object for the row            final SipProfile account = new SipProfile(cursor);            AccountRowTag tagIndicator = new AccountRowTag();            tagIndicator.accountId = account.id;            tagIndicator.activated = account.active;            tagView.indicator.setTag(tagIndicator);            tagView.indicator.setVisibility(draggable ? View.GONE : View.VISIBLE);            tagView.grabber.setVisibility(draggable ? View.VISIBLE : View.GONE);            // Get the status of this profile            tagView.labelView.setText(account.display_name);            // 1. 更新用户状态和颜色 , 到此用户的注册流程结束            // Update status label and color            if (account.active) {                AccountStatusDisplay accountStatusDisplay = AccountListUtils.getAccountDisplay(context,                        account.id);                tagView.statusView.setText(accountStatusDisplay.statusLabel);                tagView.labelView.setTextColor(accountStatusDisplay.statusColor);                // Update checkbox selection                tagView.activeCheckbox.setChecked(true);                tagView.barOnOff.setImageResource(accountStatusDisplay.checkBoxIndicator);            } else {                tagView.statusView.setText(R.string.acct_inactive);                tagView.labelView.setTextColor(mContext.getResources().getColor(                        R.color.account_inactive));                // Update checkbox selection                tagView.activeCheckbox.setChecked(false);                tagView.barOnOff.setImageResource(R.drawable.ic_indicator_off);            }            // Update account image            final WizardInfo wizardInfos = WizardUtils.getWizardClass(account.wizard);            if (wizardInfos != null) {                tagView.activeCheckbox.setBackgroundResource(wizardInfos.icon);            }        }    }