Exception is:java.lang.IllegalStateException: should not be called from the main thread

Exception is:java.lang.IllegalStateException: should not be called from the main thread

1. Description

  1. 问题描述:
  2. 对设备进行加密设置
  3. 为设备设置pin密码
  4. 进入setting 进行系统升级,系统要求输入设置的密码并进行验证
  5. 输入正确密码,系统依然会提示密码错误
  6. 问题描述:
  7. 对设备进行加密设置
  8. 为设备设置pattern密码
  9. 进入setting 进行系统升级,系统要求输入设置的密码并进行验证
  10. 会要求用户输入text类型密码

操作步骤:
问题一(reproduction procedure):

  1. encryption the terminal
  2. set secreen lock to pin with “secure start-up” to YES
  3. copy PFU file to “/storage/emulated/0/”
  4. select PFU file in system update
  5. the pin input dialer will pop up
  6. enter pin and select ok
  7. the warning dialer of a wrong password will pop up


问题二(reproduction procedure):

  1. encryption the terminal
  2. set secreen lock to pattern with “secure start-up” to YES
  3. copy PFU file to “/storage/emulated/0/”
  4. select PFU file in system update
  5. the password input dialer will pop up

结果如下图:

2. Analysis

问题一:

  1. 运行时抓取到log发现在进行密码判断时 会抛出异常Exception is:java.lang.IllegalStateException: should not be called from the main thread
11-14 12:16:45.057 8922 8922 D SdcardUpgradeAct:the pwd2 is:1234
11-14 12:16:45.057 8922 8922 D SdcardUpgradeAct: the userId is:0
11-14 12:16:45.058 8922 8922 D SdcardUpgradeAct: the verifyUserPassword Exception is:java.lang.IllegalStateException: should not be called from the main thread.
  1. 然后查看到问题根源是在升级校验的apk中调用了framework层的checkPassword方法验证 用户输入的密码,而该方法是不能直接在主线程中使用的,否则throwIfCalledOnMainThread就会主动的抛出异常中断校验过程。所以即使输入的是正确的密码,该apk也会提示password不正确.
   /**
    * Check to see if a password matches the saved password. If no password exists,
    * always returns true.
    * @param password The password to check.
    * @return Whether the password matches the stored one.
    */
   public boolean checkPassword(String password, int userId) throws RequestThrottledException {
       return checkPassword(password, userId, null /* progressCallback */);
   }

   /**
    * Check to see if a password matches the saved password. If no password exists,
    * always returns true.
    * @param password The password to check.
    * @return Whether the password matches the stored one.
    */
   public boolean checkPassword(String password, int userId,
           @Nullable CheckCredentialProgressCallback progressCallback)
           throws RequestThrottledException {
       throwIfCalledOnMainThread();/*该方法会检查当前线程是否是主线程,如果是则会抛出异常*/
       return checkCredential(password, CREDENTIAL_TYPE_PASSWORD, userId, progressCallback);
   }
  1. 根据错误原因,解法也非常简单,可以将验证密码这个逻辑单独开一个线程来做,然后根据返回的结果来判断是继续进行系统升级还是做其他操作

问题二:

  1. 这个问题出现的原因是在这个apk 中根本就没有区分加密模式的逻辑,全都使用的是text来验证用户输入的密码,如果用户输入的密码等于当前userId在设备中存储的密码才通过验证。
  2. pin/password这两种都是text类型的密码当然可以通用,但是对于pattern则不行(虽然最终pattern密码也是以text类型存在data分区)。
  3. 所以需要在原来的基础上添加一个对pattern密码验证的功能。
  4. 默认情况下android 有三种加密模式,分别是 pin/password/pattern ,通过LockPatternUtils.java提供的getKeyguardStoredPasswordQuality方法可以获取当前用户使用的哪种加密模式。
  5. 如果getKeyguardStoredPasswordQuality方法返回的是DevicePolicyManager.PASSWORD_QUALITY_SOMETHING则说明是在pattern模式下。
  6. 然后可以使用LockPatternUtils.checkPattern来验证输入的pattern密码是否与当前用户存储的pattern密码一致。
    需要注意的是checkPattern方法要求的输入参数是List<LockPatternView.Cell>类型,具体如下:
   /**
    * Check to see if a pattern matches the saved pattern. If no pattern exists,
    * always returns true.
    * @param pattern The pattern to check.
    * @return Whether the pattern matches the stored one.
    */
   public boolean checkPattern(List<LockPatternView.Cell> pattern, int userId)
           throws RequestThrottledException {
       return checkPattern(pattern, userId, null /* progressCallback */);
   }

3. solution

**问题一大概的流程是: **

  1. 点击验证后 开线程去验证密码(耗时操作) ,根据验证结果向主线程发送相关信息
  2. 如果验证通过 让handle通知主线程 显示成功界面 升级操作需放在handle主体中做
  3. 如果验证失败 让handle通知主线程 显示验证失败界面 不做升级操作
public class SdcardUpgradeActivity extends PreferenceActivity implements OnClickListener,
                              ActivityCompat.OnRequestPermissionsResultCallback {
/*此处省略*/
/*modify for add pattern password verify  begin*/
    private boolean verifyUserPassword(String pwd) {
        final int userId = getUserId();
        boolean result = false;
        try {
            result = lockPatternUtils.checkPassword(pwd, userId);
        } catch (Exception e) {}
        return result;
    }
    private void checkEncryptAndInstall(){
        DevicePolicyManager dpm = DevicePolicyManager.create(this);
        int encryptionStatus = dpm.getStorageEncryptionStatus();
        if(DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE == encryptionStatus){
            lockPatternUtils=new LockPatternUtils(this);
            /*if indicating that encryption is active ,get currnt userid's password quality*/
            int type=lockPatternUtils.getKeyguardStoredPasswordQuality(getUserId());
            Log.d(TAG, "current password quality is :"+type);
            if(PASSWORD_QUALITY_SOMETHING==type){
                Log.d(TAG, "current password quality is pattern! use patternLayout");
               /*user pattern to verify password*/
                startPatternVerifyActivity();
           }else{
               /*user pin/password to verify password*/
                Log.d(TAG, "current password quality is pin/password! use PassWordLayout");
                createTextPassWordLayout();
           }
        }else{
            userPassword = null;
            prepareInstall(true);
        }
    }
/*modify for add pattern password verify end*/

    /*add for SdcardUpgrade to verify password without main thread restrict */
     final Handler myHandler=new Handler(){
         @Override
         public void handleMessage(Message msg) {
             if(msg.what==0x123){
  prepareInstall(true);/*the result is return, and verify is ok ,prepare Install*/;
             }else if(msg.what==0x124){/*the result is return, and verify is not ok, show error message*/
                mUtil.showDialog(mUtil.getStr(R.string.popup_dialog_title_attention),
                mUtil.getStr(R.string.popup_text_input_correct_password),
                mUtil.getStr(R.string.ok),null,null,null, null, null);
      }
         }
     };
   /*add for SdcardUpgrade to verify password without main thread restrict */
    private void checkEncryptAndInstall(){
        DevicePolicyManager dpm = DevicePolicyManager.create(this);
        int encryptionStatus = dpm.getStorageEncryptionStatus();
        if(DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE == encryptionStatus){
/*绘制密码输入界面,感觉根据单一原则可以封装为一个单独方法*/
            LinearLayout pwdLayout = new LinearLayout(this);
            pwdLayout.setOrientation(LinearLayout.VERTICAL);
            float density = getResources().getDisplayMetrics().density;
            pwdLayout.setPaddingRelative((int) (15 * density), (int) (10 * density), (int) (15 * density), (int) (10 * density));
            TextView textView = new TextView(this);
            textView.setText(mUtil.getStr(R.string.popup_dialog_desc_input_passwrod));
            textView.setTextSize(16);
            pwdLayout.addView(textView);
            final EditText editText = new EditText(this);
            editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
            editText.setTextSize(16);
            editText.setBackgroundColor(0xffF5DEB3);
            pwdLayout.addView(editText);

            new AlertDialog.Builder(this, android.R.style.Theme_Material_Light_Dialog_Alert)
                    .setTitle(mUtil.getStr(R.string.popup_dialog_title_input_passwrod))
                    .setView(pwdLayout)
                    .setPositiveButton(mUtil.getStr(R.string.ok), new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            userPassword = editText.getText().toString().trim();
                            userPassword = userPassword.equals("") ? null : userPassword;
            /*modify for SdcardUpgrade to verify password without main thread restrict */
                            if(userPassword != null){
    new Thread(new Runnable() {
        @Override
        public void run() {
       if(verifyUserPassword(userPassword)){
       myHandler.sendEmptyMessage(0x123);}
 else{
       myHandler.sendEmptyMessage(0x124);
 }
        }
    }).start();
            /*modify for SdcardUpgrade to verify password without main thread restrict*/
                            }else {
                                mUtil.showDialog(mUtil.getStr(R.string.popup_dialog_title_attention),
                                    mUtil.getStr(R.string.popup_text_input_correct_password),
                                    mUtil.getStr(R.string.ok),null,null,null, null, null);
                                }
                        }
                    })
                    .setNegativeButton(mUtil.getStr(R.string.cancel), new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            userPassword = null;
                        }
                    }).create().show();
        }else{
            userPassword = null;
            prepareInstall(true);
        }
    }
/*此处省略*/
}

**问题二大概的流程是: **

  1. 对当前用户使用的密码模式进行判断。
  2. 如果是pattern模式,则跳转到一个activity中,在该activity中绘制九宫格并让用户输入验证的密码。
  3. 在用户输入完后,将结果返回到前一个界面然后对用户输入的pattern进行验证。
  4. 和问题一样,根据返回结果通过handler作出不同的响应。

九宫格其实本质就是9个数字而已,然后根据用户点击的先后顺序来组合不同的密码,如下图:

获取到的密码是12357,所以在接收用户输入pattern的界面,可以将获取到的输入pattern以字符串12357的模式返回给上层,而在LockPatternUtils类中同时也提供了将string转为List<LockPatternView.Cell>的方法stringToPattern,具体如下:

    /**
     * Deserialize a pattern.
     * @param string The pattern serialized with {@link #patternToString}
     * @return The pattern.
     */
    public static List<LockPatternView.Cell> stringToPattern(String string) {
        if (string == null) {
            return null;
        }
        List<LockPatternView.Cell> result = Lists.newArrayList();
        final byte[] bytes = string.getBytes();
        for (int i = 0; i < bytes.length; i++) {
            byte b = (byte) (bytes[i] - '1');
            result.add(LockPatternView.Cell.of(b / 3, b % 3));
        }
        return result;
    }

然后在源代码中的修改如下:

public class SdcardUpgradeActivity extends PreferenceActivity implements OnClickListener,
                              ActivityCompat.OnRequestPermissionsResultCallback {
/*此处省略*/
/*modify for add pattern password verify  begin*/
    private boolean verifyUserPassword(String pwd) {
        final int userId = getUserId();
        boolean result = false;
        try {
            result = lockPatternUtils.checkPassword(pwd, userId);
        } catch (Exception e) {}
        return result;
    }
    private void checkEncryptAndInstall(){
        DevicePolicyManager dpm = DevicePolicyManager.create(this);
        int encryptionStatus = dpm.getStorageEncryptionStatus();
        if(DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE == encryptionStatus){
            lockPatternUtils=new LockPatternUtils(this);
            /*if indicating that encryption is active ,get currnt userid's password quality*/
            int type=lockPatternUtils.getKeyguardStoredPasswordQuality(getUserId());
            Log.d(TAG, "current password quality is :"+type);
            if(PASSWORD_QUALITY_SOMETHING==type){
                Log.d(TAG, "current password quality is pattern! use patternLayout");
               /*user pattern to verify password*/
                startPatternVerifyActivity();
           }else{
               /*user pin/password to verify password*/
                Log.d(TAG, "current password quality is pin/password! use PassWordLayout");
                createTextPassWordLayout();
           }
        }else{
            userPassword = null;
            prepareInstall(true);
        }
    }
/*modify for add pattern password verify  begin*/


/*add  for add pattern password verify  begin*/
    private void createTextPassWordLayout(){
            LinearLayout pwdLayout = new LinearLayout(this);
            pwdLayout.setOrientation(LinearLayout.VERTICAL);
            float density = getResources().getDisplayMetrics().density;
            pwdLayout.setPaddingRelative((int) (15 * density), (int) (10 * density), (int) (15 * density), (int) (10 * density));
            TextView textView = new TextView(this);
            textView.setText(mUtil.getStr(R.string.popup_dialog_desc_input_passwrod));
            textView.setTextSize(16);
            pwdLayout.addView(textView);
            final EditText editText = new EditText(this);
            editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
            editText.setTextSize(16);
            editText.setTextColor(Color.BLACK);
            pwdLayout.addView(editText);
            new AlertDialog.Builder(this, android.R.style.Theme_Material_Light_Dialog_Alert)
                    .setTitle(mUtil.getStr(R.string.popup_dialog_title_input_passwrod))
                    .setView(pwdLayout)
                    .setPositiveButton(mUtil.getStr(R.string.ok), new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            userPassword = editText.getText().toString().trim();
                            userPassword = userPassword.equals("") ? null : userPassword;
                            if(userPassword != null){
        new Thread(new Runnable() {
     @Override
     public void run() {
           if(verifyUserPassword(userPassword)){
           myHandler.sendEmptyMessage(0x123);}
     else{
           myHandler.sendEmptyMessage(0x124);
     }
     }
        }).start();
                            }else {
                                mUtil.showDialog(mUtil.getStr(R.string.popup_dialog_title_attention),
                                    mUtil.getStr(R.string.popup_text_input_correct_password),
                                    mUtil.getStr(R.string.ok),null,null,null, null, null);
                                }
                        }
                    })
                    .setNegativeButton(mUtil.getStr(R.string.cancel), new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            userPassword = null;
                        }
                    }).create().show();
    }
    private void startPatternVerifyActivity(){
        Intent intent=new Intent();
        intent.setClass(this,PatternVerifyActivity.class);/*PatternVerifyActivity是一个绘制九宫格的Activity,当用户输入完毕后,会将pattern结果以string 的形式返回回来*/
        try {
            startActivityForResult(intent,1);
        }catch (Exception e)
        {
            Log.d(TAG, "start PatternVerifyActivity Activity encunter exception:"+e);

        }
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
               switch(requestCode){
                   case 1:
                       Log.d(TAG, "PatternVerifyActivity Activity return!");
                       if(1==resultCode)
                       {
                           String patternWord=data.getStringExtra(PatternVerifyActivity.PatternWord);
                           Log.d(TAG, "PatternVerifyActivity Activity return! patternWord is:"+patternWord);
                            verifyUserPayttern(patternWord);
                       }
                       break;
                   case 2:
                       break;
                }
    }
       private void verifyUserPayttern(String patternWord)
       {
           List<LockPatternView.Cell> result=lockPatternUtils.stringToPattern(patternWord);
   new Thread(new Runnable() {
                              @Override
                              public void run() {
    try {
                                   if(lockPatternUtils.checkPattern(result,getUserId())){
                                      myHandler.sendEmptyMessage(0x123);}
                                   else{
                                      myHandler.sendEmptyMessage(0x124);
                                   }
                                } catch (Exception e) {
    Log.d(TAG, "Exception is:"+e);
    }
               }
   }).start();
       }
/*add  for add pattern password verify*/
/*此处省略*/
}

然后绘制九宫格的方案网上有很多,此处参考轻松实现Android自定义九宫格图案解锁博客的简单做法,绘制一个九宫格然后记录用户的输入pattern并以string返回SdcardUpgradeActivity处理。

4. summary

这个问题引起的主要原因应该是后续framework更新导致,更新加强了对密码验证接口使用的条件。同时,验证密码部分的逻辑不够严谨,将耗时的操作放在了主线程中,这样也很容易发生anr问题。所有解法也简单,就是将这个操作拿出来,单开一个线程做就可以了。
而第二个问题就是添加一个pattern模式的验证就可以解决的,最开始做的时候,本来还想调用setting下面的绘制九宫格的逻辑,将整个绘制、验证的逻辑外包给setting来做,但是最后没有找到相关的接口 ,然后看网上很多九宫格绘制的方案,里面的逻辑也不复杂,并且主要的是android中已经有了将string转化为List<LockPatternView.Cell>的接口,解决了从用户处获取到的pattern密码与系统存储的pattern密码格式不匹配的问题。

在这里插入图片描述

发布了38 篇原创文章 · 获赞 14 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/cw102055234/article/details/84528902