目录
一、关于SystemUI的需求和开发中遇到的bug
二、需求分析
三、代码实现
四、学习产出
- @*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上做任何修改,因为电池电量需要经过一段时间的充电统计才能出来。
以下是电池管理相关的知识点:
本文仅代表个人观点和经验,难免存在不足之处。如果有任何错误或改进的建议,欢迎指正和交流,共同进步。