Android 11 SystemUI(状态/导航栏)-状态栏下拉时图标的隐藏与通知面板的半透黑色背景

Posted ansondroider

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 11 SystemUI(状态/导航栏)-状态栏下拉时图标的隐藏与通知面板的半透黑色背景相关的知识,希望对你有一定的参考价值。

概述

本文续自:Android 11 的状态栏的隐藏

PS
本文虽然同属于SystemUI, 但目前并 没有 打算整理成专橍或撰写一个系列的想法.
仅仅为了记录一些过程, 留下那些容易被遗忘的点滴.

开始下拉时状态栏图标被隐藏

   状态橍的图标在用户开始触摸(ACTION_DOWN)后, 会开始展开, 显示扩展面板, 同时, 隐藏状态橍上的通知和状态图标. 在手机上表现有可能不同, 在android 13上, 在点击没有作用, 只有下拉一定的距离,才会开始隐藏.

device-2022-12-21-190046

隐藏从触摸下按开始, 参照下图:

忽略过程代码, 直接上最后一部分:

frameworks/base/packages/SystemUI//src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java

    @Override
    public void disable(int displayId, int state1, int state2, boolean animate) 
        if (displayId != getContext().getDisplayId()) 
            return;
        
        state1 = adjustDisableFlags(state1);
        final int old1 = mDisabled1;
        final int diff1 = state1 ^ old1;
        mDisabled1 = state1;
        if ((diff1 & DISABLE_SYSTEM_INFO) != 0) 
            if ((state1 & DISABLE_SYSTEM_INFO) != 0) 
                hideSystemIconArea(animate);
                hideOperatorName(animate);
             else 
                showSystemIconArea(animate);
                showOperatorName(animate);
            
        
        if ((diff1 & DISABLE_NOTIFICATION_ICONS) != 0) 
            if ((state1 & DISABLE_NOTIFICATION_ICONS) != 0) 
                hideNotificationIconArea(animate);
             else 
                showNotificationIconArea(animate);
            
        
        // The clock may have already been hidden, but we might want to shift its
        // visibility to GONE from INVISIBLE or vice versa
        if ((diff1 & DISABLE_CLOCK) != 0 || mClockView.getVisibility() != clockHiddenMode()) 
            if ((state1 & DISABLE_CLOCK) != 0) 
                hideClock(animate);
             else 
                showClock(animate);
            
        
    
    protected int adjustDisableFlags(int state) 
        boolean headsUpVisible = mStatusBarComponent.headsUpShouldBeVisible();
        if (headsUpVisible) 
            state |= DISABLE_CLOCK;
        

        if (!mKeyguardStateController.isLaunchTransitionFadingAway()
                && !mKeyguardStateController.isKeyguardFadingAway()
                && shouldHideNotificationIcons()
                && !(mStatusBarStateController.getState() == StatusBarState.KEYGUARD
                        && headsUpVisible)) 
            state |= DISABLE_NOTIFICATION_ICONS;
            state |= DISABLE_SYSTEM_INFO;
            state |= DISABLE_CLOCK;
        


        if (mNetworkController != null && EncryptionHelper.IS_DATA_ENCRYPTED) 
            if (mNetworkController.hasEmergencyCryptKeeperText()) 
                state |= DISABLE_NOTIFICATION_ICONS;
            
            if (!mNetworkController.isRadioOn()) 
                state |= DISABLE_SYSTEM_INFO;
            
        

        // The shelf will be hidden when dozing with a custom clock, we must show notification
        // icons in this occasion.
        if (mStatusBarStateController.isDozing()
                && mStatusBarComponent.getPanelController().hasCustomClock()) 
            state |= DISABLE_CLOCK | DISABLE_SYSTEM_INFO;
        

        return state;
    

关于CollapsedStatusBarFragment

    在读到CollapsedStatusBarFragment相关代码的时候, 一时没办法把Fragment与SystemUI串联起来, 这源于传统Fragment的使用习惯: 在Activity中, 获取一个FragmentManager, 创建各种Fragment, 调用replace, show, hide…
 
    难道SystemUI也吃这一套? 状态栏和导航栏不一直是通过WindowManager.addView()直接往里面丢的么!
 
    CollapsedStatusBarFragment 本身没什么特别的, 它只是一个android.app.Fragment, 没有特殊的地方, 特殊的是FragmentController, 它与传统的Fragment使用方法完全不同, 参考 frameworks/base/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
编写一段测试代码, 以便更直观了解它的用法:

public class TestActivity extends Activity
	void testFragmentController(Context ctx, Handler h, int winAnim)
		if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) 
			FragmentController fragCtrl = FragmentController.createController(new FragmentHostCallback<Object>(ctx, h, 0) 
				@Override
				public Object onGetHost() 
					return null;
				

				@Override
				public <T extends View> T onFindViewById(int id) 
					return findViewById(id);
				
			);
			//java.lang.IllegalStateException: Activity has been destroyed
			//    at android.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1913)
			fragCtrl.attachHost(null);
			fragCtrl.dispatchCreate();
			fragCtrl.dispatchStart();
			fragCtrl.dispatchResume();

			//java.lang.IllegalArgumentException: No view found for id 0x7f08007c (com.android.factorytest:id/flRoot) for fragment SimpleFragment2b5da47 #0 id=0x7f08007c
			fragCtrl.getFragmentManager()
					.beginTransaction()
					.replace(R.id.flRoot, new SimpleFragment())
					.commit();
		
	

	public static class SimpleFragment extends Fragment
		@Override
		public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 
			TextView tv = new TextView(getActivity());
			tv.setLayoutParams(new ViewGroup.LayoutParams(UiTools.MATCH_PARENT, UiTools.MATCH_PARENT));
			tv.setText("FragmentController");
			tv.setTextSize(36);
			return tv;
		
	

重点关注代码中的createControllerFragmentHostCallback, 当创建完成后, 便可以通过 FragmentControllergetFragmentManager获取FragmentManager, 接下来就是熟悉的操作, 不多描述.

 

下拉通知黑色背景

    在下拉通知面板的过程中, 存在两部分的半透明背景, 第一部分(上)比较明显, 第二部分比较隐藏晦.
效果如下图:


这是一个自定义的View, 从layout文件中可以找到它: ScrimView

frameworks/base/packages/SystemUI/res/layout/super_notification_shade.xml

<com.android.systemui.statusbar.phone.NotificationShadeWindowView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:sysui="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">
    <!-- 省略代码 -->
    <com.android.systemui.statusbar.ScrimView
        android:id="@+id/scrim_behind"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:importantForAccessibility="no"
        sysui:ignoreRightInset="true"
        />

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java

diff --git a/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java b/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
index 7f30009cda..907d58c267 100644
--- a/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
+++ b/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
@@ -69,7 +69,7 @@ public class ScrimView extends View 
 
     @Override
     protected void onDraw(Canvas canvas) 
-        if (mDrawable.getAlpha() > 0) 
+        if (false && mDrawable.getAlpha() > 0) //强制不进行绘制, 就不会出现半透明背景
             mDrawable.draw(canvas);
         
     

第二部分只有在下拉到底部时才会出现(不仔细分辨很难看出来):

同样, 来自另一个自定义控件:AlphaOptimizedView

frameworks\\base\\packages\\SystemUI\\res\\layout\\status_bar_expanded.xml

    <com.android.systemui.statusbar.AlphaOptimizedView
        android:id="@+id/qs_navbar_scrim"
        android:layout_height="66dp"
        android:layout_width="match_parent"
        android:layout_gravity="bottom"
        android:visibility="invisible"
        android:background="@drawable/qs_navbar_scrim" />

扩展

Dagger, 绕得脑壳疼, 记录下out下的路径

out/soong/.intermediates/frameworks/base/packages/SystemUI/SystemUI-core/android_common/kapt/gen/sources/com/android/systemui/dagger/DaggerSystemUIRootComponent.java

public final class DaggerSystemUIRootComponent implements SystemUIRootComponent 

  private Provider<SystemUIRootComponent> systemUIRootComponentProvider;
    this.systemUIRootComponentProvider = InstanceFactory.create((SystemUIRootComponent) this);
    
  private Provider<FragmentService> fragmentServiceProvider;
 this.fragmentServiceProvider =
        DoubleCheck.provider(
            FragmentService_Factory.create(
                systemUIRootComponentProvider, provideConfigurationControllerProvider));

frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java

    @NonNull
    @Override
    public Application instantiateApplicationCompat(
            @NonNull ClassLoader cl, @NonNull String className)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException 
        Application app = super.instantiateApplicationCompat(cl, className);
        if (app instanceof ContextInitializer) 
            ((ContextInitializer) app).setContextAvailableCallback(
                    context -> 
                        SystemUIFactory.createFromConfig(context);
                        SystemUIFactory.getInstance().getRootComponent().inject(
                                SystemUIAppComponentFactory.this);
                    
            );
        

        return app;
    

frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java

    private void init(Context context) 
        mRootComponent = buildSystemUIRootComponent(context);

        // Every other part of our codebase currently relies on Dependency, so we
        // really need to ensure the Dependency gets initialized early on.

        Dependency dependency = new Dependency();
        mRootComponent.createDependency().createSystemUI(dependency);
        dependency.start();
    

    public static void createFromConfig(Context context) 
        if (mFactory != null) 
            return;
        

        final String clsName = context.getString(R.string.config_systemUIFactoryComponent);
        if (clsName == null || clsName.length() == 0) 
            throw new RuntimeException("No SystemUIFactory component configured");
        

        try 
            Class<?> cls = null;
            cls = context.getClassLoader().loadClass(clsName);
            mFactory = (SystemUIFactory) cls.newInstance();
            mFactory.init(context);
         catch (Throwable t) 
            Log.w(TAG, "Error creating SystemUIFactory component: " + clsName, t);
            throw new RuntimeException(t);
        
    

frameworks/base/packages/SystemUI/src/com/android/systemui/Dependency.java

    @Inject Lazy<FragmentService> mFragmentService;
        protected void start() 
        	mProviders.put(FragmentService.class, mFragmentService::get);
        

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java

FragmentHostManager.get(mPhoneStatusBarWindow)

frameworks/base/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java

    public static FragmentHostManager get(View view) 
        try 
            return Dependency.get(FragmentService.class).getFragmentHostManager(view);
         catch (ClassCastException e) 
            // TODO: Some auto handling here?
            throw e;
        
    

frameworks/base/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java

    public FragmentHostManager getFragmentHostManager(View view) 
        View root = view.getRootView();
        FragmentHostState state = mHosts.get(root);
        if (state == null) 
            state = new FragmentHostState(root);
            mHosts.put(root, state);
        
        return state.getFragmentHostManager();
    

参考

SystemUI源码分析之PhoneStatusBar初始化布局简单分析
Android SystemUI 状态栏网络图标显示分析(Android 11)
SystemUI之状态图标控制
Android 8.0 SystemUI(三):一说顶部 StatusBar
Android 8.0 SystemUI(四):二说顶部 StatusBar
Dagger 基础知识
在 Android 应用中使用 Dagger

android SystemUI 流程分析

android4 SystemUI 流程分析

 

什么是SystemUI?

对于Phone来说SystemUI指的是:StatusBar(状态栏)、NavigationBar(导航栏)。而对于Tablet或者是TV来说SystemUI指的是:
CombinedBar(包括了StatusBar和NavigationBar)。

启动后Phone界面上的信号,蓝牙标志,Wifi标志等等这些状态显示标志都会在StatusBar上显示。当我们的设备开机后,首先
需要给用户呈现的就是各种界面同时也包括了我们的SystemUI,因此对于整个Android系统来说,SystemUI都有举足轻重的作用。


现在就从代码开始一步步的分析


1、启动流程


代码路径:fameworks/base/packages/SystemUI

建立工程导入到eclipse中代码具体图示:

技术分享

 

先从 AndroidManifest.xml 看看有哪些东东,以前说过Android中有四大组件,这里就有如下的三大部分:


系统服务 Service :


SystemUIService
TakeScreenshotService
LoadAverageService


广播接收器 BroadcastReceive:


BootReceiver


Activity 应用:


USB的挺多哟...
UsbStorageActivity
UsbConfirmActivity
UsbPermissionActivity
UsbStorageActivity
UsbAccessoryUriActivity


NetworkOverLimitActivity


<!-- started from ... somewhere -->
Nyandroid


具体定义请看 AndroidManifest.xml 文件,上面只是简单的列一下


先看第一个Activity -- Nyandroid 这里做了什么呢?
就是网上传说中的 好多安卓机器人飞过去。。。。其中代码很简单,简单说一下动画效果的代码:

 

[java] view plain copy
 
 print?
  1. <span style="font-size:14px">public class FlyingCat extends ImageView {  
  2.   
  3.     public FlyingCat(Context context, AttributeSet as) {  
  4.       super(context, as);  
  5.       setImageResource(R.drawable.nyandroid_anim); // @@@  
  6.   
  7.       if (DEBUG) setBackgroundColor(0x80FF0000);  
  8.   }  
  9.   ...  
  10. }</span>  


定义在 frameworks\base\packages\SystemUI\res\drawable\nyandroid_anim.xml

 

 

 

[html] view plain copy
 
 print?
  1. <span style="font-size:14px"><animation-list  
  2.         xmlns:android="http://schemas.android.com/apk/res/android"  
  3.         android:oneshot="false">  
  4.     <item android:drawable="@drawable/nyandroid00" android:duration="80" />  
  5.     <item android:drawable="@drawable/nyandroid01" android:duration="80" />  
  6.     <item android:drawable="@drawable/nyandroid02" android:duration="80" />  
  7.     <item android:drawable="@drawable/nyandroid03" android:duration="80" />  
  8.     <item android:drawable="@drawable/nyandroid04" android:duration="80" />  
  9.     <item android:drawable="@drawable/nyandroid05" android:duration="80" />  
  10.     <item android:drawable="@drawable/nyandroid06" android:duration="80" />  
  11.     <item android:drawable="@drawable/nyandroid07" android:duration="80" />  
  12.     <item android:drawable="@drawable/nyandroid08" android:duration="80" />  
  13.     <item android:drawable="@drawable/nyandroid09" android:duration="80" />  
  14.     <item android:drawable="@drawable/nyandroid10" android:duration="80" />  
  15.     <item android:drawable="@drawable/nyandroid11" android:duration="80" />  
  16. </animation-list></span>  


相关图片在: frameworks\base\packages\SystemUI\res\drawable-nodpi  如图示:


然后再看最重要的服务:SystemUIService


一般来说,Service启动一般由开机广播或者StartService/BindService这几种方式来启动。既然这个Service是一个系统
服务,应该是由系统这边启动,那么看下 SystemServer.java ,果然发现如下启动代码:

 

 

 

[java] view plain copy
 
 print?
  1. <span style="font-size:14px">startSystemUi(contextF);  
  2.   
  3. static final void startSystemUi(Context context) {  
  4.     Intent intent = new Intent();  
  5.     intent.setComponent(new ComponentName("com.android.systemui",  
  6.                 "com.android.systemui.SystemUIService"));  
  7.     Slog.d(TAG, "Starting service: " + intent);  
  8.     context.startService(intent);  
  9. }</span>  


对于Android启动流程请看如下系统文章:

 


http://blog.csdn.net/andyhuabing/article/details/7346203  android启动--深入理解init进程
http://blog.csdn.net/andyhuabing/article/details/7349986  android启动--深入理解zygote
http://blog.csdn.net/andyhuabing/article/details/7351691  android启动--深入理解zygote (II)
http://blog.csdn.net/andyhuabing/article/details/7353910  android启动--深入理解启动HOME

 

那么就继续跟踪 SystemUIService 中代码:

 

 

[java] view plain copy
 
 print?
  1. <span style="font-size:14px">/** 
  2.  * The class names of the stuff to start. 
  3.  */  
  4. final Object[] SERVICES = new Object[] {  
  5.         0, // system bar or status bar, filled in below.  
  6.         com.android.systemui.power.PowerUI.class,  
  7. };  
  8.   
  9. @Override  
  10. public void onCreate() {  
  11.     // Pick status bar or system bar.  
  12.     IWindowManager wm = IWindowManager.Stub.asInterface(  
  13.             ServiceManager.getService(Context.WINDOW_SERVICE));  
  14.     try {  
  15.         SERVICES[0] = wm.canStatusBarHide()  
  16.                 ? R.string.config_statusBarComponent  
  17.                 : R.string.config_systemBarComponent;  
  18.     } catch (RemoteException e) {  
  19.         Slog.w(TAG, "Failing checking whether status bar can hide", e);  
  20.     }  
  21.   
  22.     final int N = SERVICES.length;  
  23.     mServices = new SystemUI[N];  
  24.     for (int i=0; i<N; i++) {  
  25.         Class cl = chooseClass(SERVICES[i]);  
  26.         Slog.d(TAG, "loading: " + cl);  
  27.         try {  
  28.             mServices[i] = (SystemUI)cl.newInstance();  
  29.         } catch (IllegalAccessException ex) {  
  30.             throw new RuntimeException(ex);  
  31.         } catch (InstantiationException ex) {  
  32.             throw new RuntimeException(ex);  
  33.         }  
  34.         mServices[i].mContext = this;  
  35.         Slog.d(TAG, "running: " + mServices[i]);  
  36.         mServices[i].start();  
  37.     }  
  38. }</span>  


在这代码中:

 

 

[java] view plain copy
 
 print?
  1. <span style="font-size:14px">SERVICES[0] = wm.canStatusBarHide()  
  2.         ? R.string.config_statusBarComponent  
  3.         : R.string.config_systemBarComponent;  
  4. </span>  


通过AIDL获取WindowManager对象并调用 wm.canStatusBarHide() 这个代码在哪里呢?


查看: frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java

 

 

 

[java] view plain copy
 
 print?
  1. <span style="font-size:14px">    public boolean canStatusBarHide() {  
  2.         return mStatusBarCanHide;  
  3.     }  
  4.       
  5.     public void setInitialDisplaySize(int width, int height) {  
  6.     ...  
  7.         // Determine whether the status bar can hide based on the size  
  8.         // of the screen.  We assume sizes > 600dp are tablets where we  
  9.         // will use the system bar.  
  10.         int shortSizeDp = shortSize  
  11.                 * DisplayMetrics.DENSITY_DEFAULT  
  12.                 / DisplayMetrics.DENSITY_DEVICE;  
  13.         mStatusBarCanHide = shortSizeDp < 600;  
  14.         
  15.         }</span>  


从以上代码来看,shortSizeDp小于600dp时,则系统会认为该设备是Phone反之则认为是Tablet。
根据mStatusBarCanHide的值,设定StatusBar或者SystemBar(CombinedBar)的高度,以及是否显示NavigationBar。


2、StatusBar(状态栏)及NavigationBar(导航栏)


如果是 StatusBar 则 SERVICES[0] 存放 com.android.systemui.statusbar.phone.PhoneStatusBar 否则存放 
com.android.systemui.statusbar.tablet.TabletStatusBar 
SERVICES[1] 存放 com.android.systemui.power.PowerUI.class


从我的机器上打印来看,
E/SystemServer( 1299): Starting service: Intent { cmp=com.android.systemui/.SystemUIService }


D/SystemUIService( 1382): running: [email protected]
D/SystemUIService( 1382): running: [email protected]
I/PowerUI ( 1382): start


然后调用 mServices[i].start();那么就分析 TabletStatusBar 中的start方法吧

 

 

 

[java] view plain copy
 
 print?
  1. <span style="font-size:14px">    @Override  
  2.     public void start() {  
  3.         super.start(); // will add the main bar view  
  4.     }</span>  



 

调用到 frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.Java 

 

[java] view plain copy
 
 print?
  1. <span style="font-size:14px">public void start() {  
  2.         // First set up our views and stuff.  
  3.         View sb = makeStatusBarView();  
  4.   
  5.         // Connect in to the status bar manager service  
  6.         StatusBarIconList iconList = new StatusBarIconList();  
  7.         ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>();  
  8.         ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>();  
  9.         mCommandQueue = new CommandQueue(this, iconList);  
  10.         mBarService = IStatusBarService.Stub.asInterface(  
  11.                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));  
  12.         int[] switches = new int[7];  
  13.         ArrayList<IBinder> binders = new ArrayList<IBinder>();  
  14.         try {  
  15.             mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,  
  16.                     switches, binders);  
  17.         } catch (RemoteException ex) {  
  18.             // If the system process isn‘t there we‘re doomed anyway.  
  19.         }  
  20.   
  21.         disable(switches[0]);  
  22.         setSystemUiVisibility(switches[1]);  
  23.         topAppWindowChanged(switches[2] != 0);  
  24.         // StatusBarManagerService has a back up of IME token and it‘s restored here.  
  25.         setImeWindowStatus(binders.get(0), switches[3], switches[4]);  
  26.         setHardKeyboardStatus(switches[5] != 0, switches[6] != 0);  
  27.   
  28.         // Set up the initial icon state  
  29.         int N = iconList.size();  
  30.         int viewIndex = 0;  
  31.         for (int i=0; i<N; i++) {  
  32.             StatusBarIcon icon = iconList.getIcon(i);  
  33.             if (icon != null) {  
  34.                 addIcon(iconList.getSlot(i), i, viewIndex, icon);  
  35.                 viewIndex++;  
  36.             }  
  37.         }  
  38.   
  39.         // Set up the initial notification state  
  40.         N = notificationKeys.size();  
  41.         if (N == notifications.size()) {  
  42.             for (int i=0; i<N; i++) {  
  43.                 addNotification(notificationKeys.get(i), notifications.get(i));  
  44.             }  
  45.         } else {  
  46.             Log.wtf(TAG, "Notification list length mismatch: keys=" + N  
  47.                     + " notifications=" + notifications.size());  
  48.         }  
  49.   
  50.         // Put up the view  
  51.         final int height = getStatusBarHeight();  
  52.   
  53.         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(  
  54.                 ViewGroup.LayoutParams.MATCH_PARENT,  
  55.                 height,  
  56.                 WindowManager.LayoutParams.TYPE_STATUS_BAR,  
  57.                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE  
  58.                     | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING  
  59.                     | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,  
  60.                 PixelFormat.OPAQUE);  
  61.           
  62.         // the status bar should be in an overlay if possible  
  63.         final Display defaultDisplay   
  64.             = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))  
  65.                 .getDefaultDisplay();  
  66.         if (ActivityManager.isHighEndGfx(defaultDisplay)) {  
  67.             lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;  
  68.         }  
  69.   
  70.         lp.gravity = getStatusBarGravity();  
  71.         lp.setTitle("StatusBar");  
  72.         lp.packageName = mContext.getPackageName();  
  73.         lp.windowAnimations = R.style.Animation_StatusBar;  
  74.         WindowManagerImpl.getDefault().addView(sb, lp);  
  75.   
  76.         if (SPEW) {  
  77.             Slog.d(TAG, "Added status bar view: gravity=0x" + Integer.toHexString(lp.gravity)   
  78.                    + " icons=" + iconList.size()  
  79.                    + " disabled=0x" + Integer.toHexString(switches[0])  
  80.                    + " lights=" + switches[1]  
  81.                    + " menu=" + switches[2]  
  82.                    + " imeButton=" + switches[3]  
  83.                    );  
  84.         }  
  85.   
  86.         mDoNotDisturb = new DoNotDisturb(mContext);  
  87.     }</span>  


在这里,完成了SystemUI的整个初始化以及设置过程,并最终呈现到界面上。
启动过程中完成如下操作:
1、获取icon list,addIcon(iconList.getSlot(i), i, viewIndex, icon);
2、获取notification,addNotification(notificationKeys.get(i), notifications.get(i));
3、显示StatusBar,WindowManagerImpl.getDefault().addView(sb, lp);
   显示NavigationBar,WindowManagerImpl.getDefault().addView(
                mNavigationBarView, getNavigationBarLayoutParams());


时序图如下:

 

技术分享

 

3、最近任务缩略图显示 


长按home键,列出最近启动过的任务缩略图,重要的两个类


// Recent apps
private RecentsPanelView mRecentsPanel;
private RecentTasksLoader mRecentTasksLoader;


SystemUI 获取按键事件,获取缩略图并将其显示出来,最后响应view上按键响应相应事件:


对于我们来说,关注点主要有如下几个:


1、缩略图如何获取

 

 

[java] view plain copy
 
 print?
  1. <span style="font-size:14px">RecentsPanelView.java 中  
  2. refreshRecentTasksList(recentTaskDescriptions);   
  3. -->  
  4. mRecentTaskDescriptions = mRecentTasksLoader.getRecentTasks();  
  5. -->   
  6. RecentTasksLoader.java 中  
  7.   
  8.         // return a snapshot of the current list of recent apps  
  9.     ArrayList<TaskDescription> getRecentTasks() {  
  10.         cancelLoadingThumbnails();  
  11.   
  12.         ArrayList<TaskDescription> tasks = new ArrayList<TaskDescription>();  
  13.         final PackageManager pm = mContext.getPackageManager();  
  14.         final ActivityManager am = (ActivityManager)  
  15.                 mContext.getSystemService(Context.ACTIVITY_SERVICE);  
  16.   
  17.         final List<ActivityManager.RecentTaskInfo> recentTasks =  
  18.                 am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE);  
  19.   
  20.         ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)  
  21.                     .resolveActivityInfo(pm, 0);  
  22.   
  23.         HashSet<Integer> recentTasksToKeepInCache = new HashSet<Integer>();  
  24.         int numTasks = recentTasks.size();  
  25.   
  26.         // skip the first task - assume it‘s either the home screen or the current activity.  
  27.         final int first = 1;  
  28.         recentTasksToKeepInCache.add(recentTasks.get(0).persistentId);  
  29.         for (int i = first, index = 0; i < numTasks && (index < MAX_TASKS); ++i) {  
  30.             final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(i);  
  31.   
  32.             TaskDescription item = createTaskDescription(recentInfo.id,  
  33.                     recentInfo.persistentId, recentInfo.baseIntent,  
  34.                     recentInfo.origActivity, recentInfo.description, homeInfo);  
  35.   
  36.             if (item != null) {  
  37.                 tasks.add(item);  
  38.                 ++index;  
  39.             }  
  40.         }  
  41.   
  42.         // when we‘re not using the TaskDescription cache, we load the thumbnails in the  
  43.         // background  
  44.         loadThumbnailsInBackground(new ArrayList<TaskDescription>(tasks));  
  45.         return tasks;  
  46.     }  
  47. </span>  


这里利用 ActivityManager 中的方法:getRecentTasks 获取当前任务的列表,然后再利用 getTaskThumbnails 获取


按键View  就是几个按键相应的View

 

 

[java] view plain copy
 
 print?
  1. <span style="font-size:14px">    public View getRecentsButton() {  
  2.         return mCurrentView.findViewById(R.id.recent_apps);  
  3.     }  
  4.   
  5.     public View getMenuButton() {  
  6.         return mCurrentView.findViewById(R.id.menu);  
  7.     }  
  8.   
  9.     public View getBackButton() {  
  10.         return mCurrentView.findViewById(R.id.back);  
  11.     }  
  12.   
  13.     public View getHomeButton() {  
  14.         return mCurrentView.findViewById(R.id.home);  
  15.     }</span>  


相应的应用缩略图,调用序列图如下:

 

技术分享

 

2、显示缩略图
public void show(boolean show, boolean animate,
            ArrayList<TaskDescription> recentTaskDescriptions) {
        if (show) {
            // Need to update list of recent apps before we set visibility so this view‘s
            // content description is updated before it gets focus for TalkBack mode
            refreshRecentTasksList(recentTaskDescriptions);


            // if there are no apps, either bring up a "No recent apps" message, or just
            // quit early
            boolean noApps = (mRecentTaskDescriptions.size() == 0);
            if (mRecentsNoApps != null) { // doesn‘t exist on large devices
                mRecentsNoApps.setVisibility(noApps ? View.VISIBLE : View.INVISIBLE);
            } else {
                if (noApps) {
                    if (DEBUG) Log.v(TAG, "Nothing to show");
                    return;
                }
            }
         }else {
            mRecentTasksLoader.cancelLoadingThumbnails();
            mRecentTasksDirty = true;
         }
         ...
}


如果 mRecentsNoApps 为空则表示没有任务,显示 "No recent apps" 否则显示应用列表
否则则显示任务的缩略图。时序图如下:

技术分享

 

3、点击某个缩略图执行

这里分为点击某个缩略图执行程序及长按缩略图执行程序


这里直接继承了 View.OnItemClickListener 所以可以直接执行子项按键事件

 

[java] view plain copy
 
 print?
  1. public class RecentsPanelView extends RelativeLayout implements OnItemClickListener, RecentsCallback,  
  2.         StatusBarPanel, Animator.AnimatorListener, View.OnTouchListener   


处理点击事件方法:

 

 

[java] view plain copy
 
 print?
  1. public void onItemClick(AdapterView<?> parent, View view, int position, long id) {  
  2.     handleOnClick(view);  
  3. }  
  4.   
  5.   public void handleOnClick(View view) {  
  6.       TaskDescription ad = ((ViewHolder) view.getTag()).taskDescription;  
  7.       final Context context = view.getContext();  
  8.       final ActivityManager am = (ActivityManager)  
  9.               context.getSystemService(Context.ACTIVITY_SERVICE);  
  10.       if (ad.taskId >= 0) {  
  11.           // This is an active task; it should just go to the foreground.  
  12.           am.moveTaskToFront(ad.taskId, ActivityManager.MOVE_TASK_WITH_HOME);  
  13.       } else {  
  14.           Intent intent = ad.intent;  
  15.           intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY  
  16.                   | Intent.FLAG_ACTIVITY_TASK_ON_HOME  
  17.                   | Intent.FLAG_ACTIVITY_NEW_TASK);  
  18.           if (DEBUG) Log.v(TAG, "Starting activity " + intent);  
  19.           context.startActivity(intent);  
  20.       }  
  21.       hide(true);  
  22.   }  
  23.     


注意代码:context.startActivity(intent);  这里就是执行对应的 Activity

 

 

处理长按键点击事件方法:

 

[java] view plain copy
 
 print?
  1. public void handleLongPress(  
  2.           final View selectedView, final View anchorView, final View thumbnailView) {  
  3.       thumbnailView.setSelected(true);  
  4.       PopupMenu popup = new PopupMenu(mContext, anchorView == null ? selectedView : anchorView);  
  5.       popup.getMenuInflater().inflate(R.menu.recent_popup_menu, popup.getMenu());  
  6.       popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {  
  7.           public boolean onMenuItemClick(MenuItem item) {  
  8.               if (item.getItemId() == R.id.recent_remove_item) {  
  9.                   mRecentsContainer.removeViewInLayout(selectedView);  
  10.               } else if (item.getItemId() == R.id.recent_inspect_item) {  
  11.                   ViewHolder viewHolder = (ViewHolder) selectedView.getTag();  
  12.                   if (viewHolder != null) {  
  13.                       final TaskDescription ad = viewHolder.taskDescription;  
  14.                       startApplicationDetailsActivity(ad.packageName);  
  15.                       mBar.animateCollapse();  
  16.                   } else {  
  17.                       throw new IllegalStateException("Oops, no tag on view " + selectedView);  
  18.                   }  
  19.               } else {  
  20.                   return false;  
  21.               }  
  22.               return true;  
  23.           }  
  24.       });  
  25.       popup.setOnDismissListener(new PopupMenu.OnDismissListener() {  
  26.           public void onDismiss(PopupMenu menu) {  
  27.               thumbnailView.setSelected(false);  
  28.           }  
  29.       });  
  30.       popup.show();  
  31.   }  


这里弹出一个PopupMenu,分别是 A:"Remove from list" 及 B:"App Info"


其中A项表示将此任务移除出列表,执行 mRecentsContainer.removeViewInLayout(selectedView);


另外B是启动另外一个Acitivty列出应用信息:

 

 

[java] view plain copy
 
 print?
  1. private void startApplicationDetailsActivity(String packageName) {  
  2.     Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,  
  3.             Uri.fromParts("package", packageName, null));  
  4.     intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
  5.     getContext().startActivity(intent);  
  6. }  



 

总结:

这里详细的对SystemUI 的两个最重要的 StatusBar NavigationBar(SystemUIService) 及缩略图代码流程分析。
因此各家厂商根据自家的需求,需要定制SystemUI或者美化SystemUI,不同的平台也会有不同的修改,但大体框架是没有变的,
无非是在原有基础上的修修改改或者增加一些自己的类等等。











































































































































































以上是关于Android 11 SystemUI(状态/导航栏)-状态栏下拉时图标的隐藏与通知面板的半透黑色背景的主要内容,如果未能解决你的问题,请参考以下文章

Android系统_SystemUI_android10_添加控制底部导航栏广播

Android SystemUI 导航栏动态显示隐藏

Android SystemUI 导航栏动态显示隐藏

Android SystemUI 导航栏动态显示隐藏

Android 11.0 解决切换横屏时SystemUI导航栏固定在桌面右侧而不是底部的问题

Android 11.0 解决切换横屏时SystemUI导航栏固定在桌面右侧而不是底部的问题