Android SystemUI篇(三):实战篇

目录

一、关于SystemUI的需求和开发中遇到的bug

二、需求分析

三、代码实现

四、学习产出


一、关于SystemUI的需求和开发中遇到的bug

  1. 去掉手机上的某些功能
  2. 修改手机下拉通知栏的按键可上下操作和手机截图弹出框可按键操作
  3. 在输入pin码的解锁界面,修改back键作为删除键
  4. 在锁屏界面按下任意按键可进入解锁界面
  5. 修改手机在充电时,通知栏关于电量统计
  6. 限制pin码输入框输入字符的位数

二、需求分析

  1. 第一个需求可以通过隐藏图标的方式将该功能干掉
  2. 第二个需求在布局文件中设置相关焦点的属性或者在代码中设置焦点的相关属性
  3. 第三个需求要找到输入pin码的代码的位置,可通过全局搜索的方式来查找
  4. 第四个需求同样需要定位到按下任意按键可进入解锁界面的代码位置
  5. 第五个需求可通过通知栏中提示的电量信息从而定位到问题的代码出处
  6. 有三种方式限制editText中输入字符的个数


三、代码实现

  1. 在/SystemUI/res/values下找到config.xml文件,将不需要的功能干掉:


        @*android:string/status_bar_rotate
        





      wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast,screenrecord





      wifi,bt,dnd,flashlight,battery,cell,airplane,cast,screenrecord

        2.1 可通过为相关控件设置可获取焦点和不可获取焦点的属性,来使得下拉通知栏按键可上下操作,如下的示例:





    
    

    
    

    
    

    

    

在NotificationStackScrollLayout.java文件中添加按键可上下操作的方法,如下:

public ExpandableView getViewNextView(ExpandableView view) {
        ExpandableView previousView = null;
        int childCount = getChildCount();
        for (int i = childCount-1; i >= 0; i--) {
            View child = getChildAt(i);
			Log.e(TAG,"getViewNextView child = " + child);
			Log.e(TAG,"getViewNextView child.getVisibility() = " + child.getVisibility());
            if (child == view) {
				Log.e(TAG,"getViewNextView previousView = " + previousView);
                return previousView;
            }
            if (child.getVisibility() != View.GONE) {
                previousView = (ExpandableView) child;
            }
        }
		
        return null;
    }
	@Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
        switch (event.getKeyCode()) {
            case KeyEvent.KEYCODE_DPAD_DOWN:
			case KeyEvent.KEYCODE_DPAD_UP:
                if (down) {
					View focused = findFocus();
					if(focused != null && focused instanceof ExpandableView){
						View nextView;
						if(event.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN){
							nextView = getViewNextView((ExpandableView)focused);
						}
						else{
							nextView = getViewBeforeView((ExpandableView)focused);
						}
						int deltaY = getIntrinsicHeight(focused);
						if(nextView != null){
							deltaY += getIntrinsicHeight(nextView);
						}
						int focusIndex = getNotGoneIndex(focused);
						int count = getNotGoneChildCount();
						boolean isDealScroll = false;
						if(event.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN){
							if(focused.focusSearch(View.FOCUS_DOWN) == null && focusIndex < count-1 ){
								isDealScroll = true;
							}
						}
						else{
							if(focusIndex > 0){
								isDealScroll = true;
							}
						}
						if(isDealScroll){
							
							float scrollAmount;
							int range;
		                    if (ANCHOR_SCROLLING) {
		                        range = 0;  // unused in the methods it's being passed to
		                    } else {
		                        range = getScrollRange();
		                        if (mExpandedInThisMotion) {
		                            range = Math.min(range, mMaxScrollAfterExpand);
		                        }
		                    }
		                    //if (deltaY < 0) {
		                    //    scrollAmount = overScrollDown(deltaY);
		                    //} else {
		                        //scrollAmount = overScrollUp(deltaY, range);
		                    //}
							if(event.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN){
								customOverScrollBy(deltaY, mOwnScrollY, range,(int) (mMaxOverScroll));
							}
							else{
								customOverScrollBy(-deltaY, mOwnScrollY, range,(int) (mMaxOverScroll));
							}

		                    // Calling customOverScrollBy will call onCustomOverScrolled, which
		                    // sets the scrolling if applicable.
		                    /*if (scrollAmount != 0.0f) {
		                        // The scrolling motion could not be compensated with the
		                        // existing overScroll, we have to scroll the view
		                        customOverScrollBy((int) scrollAmount, mOwnScrollY,
		                                range, getHeight() / 2);
		                        // If we're scrolling, leavebehinds should be dismissed
		                        checkSnoozeLeavebehind();
		                    }*/
						}
					}
                }
				else{
                    endDrag();
				}
                //super.dispatchKeyEvent(event);
				return super.dispatchKeyEvent(event);
        }
        return super.dispatchKeyEvent(event);
    }

        2.2 为手机截屏添加焦点,需要首先找到截屏代码的出处,在frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot下的GlobalScreenshot.java中的startAnimation()方法中修改:

private void startAnimation(final Consumer finisher, Rect screenRect, Insets screenInsets,
            boolean showFlash) {

        // add begin
        mScreenshotLayout.clearFocus();
        mScreenshotLayout.requestFocus();
        mScreenshotLayout.setNextFocusUpId(mScreenshotPreview.getId());
        mScreenshotLayout.setNextFocusDownId(mScreenshotPreview.getId());
        mScreenshotLayout.setNextFocusLeftId(mScreenshotPreview.getId());
        mScreenshotLayout.setNextFocusRightId(mScreenshotPreview.getId());
        // add end
        
        mScreenshotHandler.post(() -> {
            if (!mScreenshotLayout.isAttachedToWindow()) {
                mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
            }
            mScreenshotAnimatedView.setImageDrawable(
                    createScreenDrawable(mScreenBitmap, screenInsets));
            setAnimatedViewSize(screenRect.width(), screenRect.height());
            // Show when the animation starts
            mScreenshotAnimatedView.setVisibility(View.GONE);

            mScreenshotPreview.setImageDrawable(createScreenDrawable(mScreenBitmap, screenInsets));
            // make static preview invisible (from gone) so we can query its location on screen
            mScreenshotPreview.setVisibility(View.INVISIBLE);

            mScreenshotHandler.post(() -> {
                mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this);

                mScreenshotAnimation =
                        createScreenshotDropInAnimation(screenRect, showFlash);

                saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
                    @Override
                    void onActionsReady(SavedImageData imageData) {
                        showUiOnActionsReady(imageData);
                    }
                });

                // Play the shutter sound to notify that we've taken a screenshot
                mCameraSound.play(MediaActionSound.SHUTTER_CLICK);

                mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                mScreenshotPreview.buildLayer();
                mScreenshotAnimation.start();
            });
        });
    }

        3.全局搜索后,在根目录下frameworks/base/packages/SystemUI/src/com/android/keyguard/找到了KeyguardPinBasedInputView.java,在代码的onKeyDown()方法中添加Back键的逻辑:

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (KeyEvent.isConfirmKey(keyCode)) {
            performClick(mOkButton);
            return true;
        } else if (keyCode == KeyEvent.KEYCODE_DEL) {
            performClick(mDeleteButton);
            return true;
        }
        if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
            int number = keyCode - KeyEvent.KEYCODE_0;
            performNumberClick(number);
            return true;
        }
        if (keyCode >= KeyEvent.KEYCODE_NUMPAD_0 && keyCode <= KeyEvent.KEYCODE_NUMPAD_9) {
            int number = keyCode - KeyEvent.KEYCODE_NUMPAD_0;
            performNumberClick(number);
            return true;
        }
		//add for back to del
        if (keyCode == KeyEvent.KEYCODE_BACK){
            if (mPasswordEntry.isEnabled()){
                mPasswordEntry.deleteLastChar();
            }
        }
        //add end
        return super.onKeyDown(keyCode, event);
    }

        4.通过全局搜索,在根目录下找到了该路径,src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController,在代码的dispatchKeyEvent()方法中实现需求:

            @Override
            public boolean dispatchKeyEvent(KeyEvent event) {
                boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
                switch (event.getKeyCode()) {
                    case KeyEvent.KEYCODE_BACK:
                        if (!down) {
                            mService.onBackPressed();
                        }
                        return true;
					//add begin
					case KeyEvent.KEYCODE_0:
					case KeyEvent.KEYCODE_1:
					case KeyEvent.KEYCODE_2:
					case KeyEvent.KEYCODE_3:
					case KeyEvent.KEYCODE_4:
					case KeyEvent.KEYCODE_5:
					case KeyEvent.KEYCODE_6:
					case KeyEvent.KEYCODE_7:
					case KeyEvent.KEYCODE_8:
					case KeyEvent.KEYCODE_9:
					case KeyEvent.KEYCODE_CAMERA:
					case KeyEvent.KEYCODE_STAR:
					case KeyEvent.KEYCODE_POUND:
					case KeyEvent.KEYCODE_CALL:
					case KeyEvent.KEYCODE_ENVELOPE:
					case KeyEvent.KEYCODE_DPAD_LEFT:
					case KeyEvent.KEYCODE_DPAD_RIGHT:
                    //add begin
                    case KeyEvent.KEYCODE_MENU:
                        if (!down) {
                            return mService.onMenuPressed();
                        }
                        break;
                    case KeyEvent.KEYCODE_SPACE:
                        if (!down) {
                            return mService.onSpacePressed();
                        }
                        break;
                    case KeyEvent.KEYCODE_VOLUME_DOWN:
                    case KeyEvent.KEYCODE_VOLUME_UP:
                        if (mStatusBarStateController.isDozing()) {
                            MediaSessionLegacyHelper.getHelper(mView.getContext())
                                    .sendVolumeKeyEvent(
                                            event, AudioManager.USE_DEFAULT_STREAM_TYPE, true);
                            return true;
                        }
                        break;
                    }
                return false;
            }
        });

          5.通过全局搜索,在SystemUI/src/com/android/systemui/statusbar下,找到KeyguardIndicationController.java,其中的computePowerIndication()方法用于统计电池电量状态:

protected String computePowerIndication() {
        if (mPowerCharged) {
            return mContext.getResources().getString(R.string.keyguard_charged);
        }

        final boolean hasChargingTime = mChargingTimeRemaining > 0;
        int chargingId;
        if (mPowerPluggedInWired) {
            switch (mChargingSpeed) {
                case BatteryStatus.CHARGING_FAST:
                    chargingId = hasChargingTime
                            ? R.string.keyguard_indication_charging_time_fast
                            : R.string.keyguard_plugged_in_charging_fast;
                    break;
                case BatteryStatus.CHARGING_SLOWLY:
                    chargingId = hasChargingTime
                            ? R.string.keyguard_indication_charging_time_slowly
                            : R.string.keyguard_plugged_in_charging_slowly;
                    break;
                default:
                    chargingId = hasChargingTime
                            ? R.string.keyguard_indication_charging_time
                            : R.string.keyguard_plugged_in;
                    break;
            }
        } else {
            chargingId = hasChargingTime
                    ? R.string.keyguard_indication_charging_time_wireless
                    : R.string.keyguard_plugged_in_wireless;
        }

        String percentage = NumberFormat.getPercentInstance()
                .format(mBatteryLevel / 100f);
        if (hasChargingTime) {
            // We now have battery percentage in these strings and it's expected that all
            // locales will also have it in the future. For now, we still have to support the old
            // format until all languages get the new translations.
            String chargingTimeFormatted = Formatter.formatShortElapsedTimeRoundingUpToMinutes(
                    mContext, mChargingTimeRemaining);
            try {
                return mContext.getResources().getString(chargingId, chargingTimeFormatted,
                        percentage);
            } catch (IllegalFormatConversionException e) {
                return mContext.getResources().getString(chargingId, chargingTimeFormatted);
            }
        } else {
            // Same as above
            try {
                return mContext.getResources().getString(chargingId, percentage);
            } catch (IllegalFormatConversionException e) {
                return mContext.getResources().getString(chargingId);
            }
        }
    }

          6.第一种方式直接设置调用dialog的setMaxLen()设置输入字符的最大长度,第二种方式可以监听使用addTextChangedListener来监听editText的字符变化,第三种方式可以对editText设置字符过滤:

//第一种方式限制textView中的字符位数
mPinDialog.setMaxLen(mPinDialog.getEditText().getText().length(),8);
//第二种方式限制textView中的字符位数	
editText.addTextChangedListener(new TextWatcher() {//this bug
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                Log.d("test1","enter this beforeTextChanged");
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                Log.d("test2","enter this onTextChanged begin");
                String str = s.toString();
                editText.setText(str.substring(0,8));
                editText.requestFocus();
                editText.setSelection(editText.getText().length());
                Log.d("test3","enter this onTextChanged end");
            }

            @Override
            public void afterTextChanged(Editable s) {
                Log.d("test4","enter this afterTextChanged");
            }
        });
//第三种方式限制textView中的字符位数
//在EditPinPreference中重写dialog()方法用于监听字符位数的变化
//并自定义字符最大和最小长度的方法
//是第一种方法和第二种方法的结合
//重写setFilters()字符过滤方法
//add begin
editText.setFilters(new InputFilter[]{new InputFilter() {
       @Override
       public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
              if ((!source.toString().equals("")) && dest.toString().length() > 8){
					Log.d("test5","pin pass limit");
                   Toast.makeText(getPrefContext().getApplicationContext(), "please enter 4-8 pass",Toast.LENGTH_SHORT).show();
                }
              return null;
            }
      },new InputFilter.LengthFilter(8)});

四、学习产出

在解决手机充电,手机电量在通知栏的显示问题时,不可以随便就把两个显示电量字符串的顺序给换过来,否则编译新版本后,开机后系统会起不来,在了解电池管理相关的知识后,可不在KeyguardIndicationController.java上做任何修改,因为电池电量需要经过一段时间的充电统计才能出来。

以下是电池管理相关的知识点:

  1. 電池管理中的IBatteryStats.aidl接口文件封裝了電池統計相關的方法:如isCharging()、computeBatteryTimeRemaining()、computeChargeTimeRemaining()和getStatistics()等方法;
  2. IBatteryStats.aidl接口文件中的方法由BatteryStatsImpl.java來實現,BatteryStatsImpl.java繼承了BatteryStats.java類,該類用於提供對電池使用統計信息的訪問等作用;
  3. BatteryStats.java類實現了Parcelable接口和BatteryStatsImpl抽象類,其中Parcelable接口用於在不同組件閒傳遞數據時進行序列化和反序列化,而BatteryStatsImpl抽系類提供了BatteryStats类的大部分实现,是实现电池统计功能的核心代码。

本文仅代表个人观点和经验,难免存在不足之处。如果有任何错误或改进的建议,欢迎指正和交流,共同进步。

你可能感兴趣的:(android基础,Java基础,android,java)