SystemUI 有哪内容
从表面上看, 我们看到的状态栏、通知栏、下拉菜单、导航栏、锁屏、最近任务、低电提示等系统页面都是 SystemUI 的。SystemUI,在源码目录中位于: framework/base/packages 目录下, 可见 SystemUI 和 framework 是关联的, SystemUI 依赖了很多内部 API , 系统资源, SystemUI 编译是要依赖系统源码的。
SystemUI 也是一个应用,不过这个应用特殊之处在于他没有启动图标、也没有入口 Activity 。他的入口程序是一个服务:SystemUIService。 这个服务会被系统服务拉起来, 这个服务起来, SystemUI 应用进程就创建起来了,具体启动过程后面会分析。除了 SystemUIService , SystemUI 还有很多服务, 例如: 负责锁屏的KeyguardService、负责最近任务的 RecentsSystemUserService、负责壁纸的 ImageWallpaper 、负责截屏的TakeScreenshotService 等。
下面是PhoneStatusBarView的view 树形图:
PanelHolder
PanelHolder是用户下拉 status bar 后得到的 view。它主要包含 QuickSettings 和 Notification panel 两个部分。PanelHolder是一个继承自FrameLayout的自定义view,它的内容是通过include status_bar_expanded.xml进行填充的。PanelHolder的布局比较复杂,为了提高view的重用性大量的使用了include标签。下面是PanelHolder的view树形图, 只给出了了主要的view:
架构关系
在系统服务中,有一个服务是专门为 SystemUI 的状态栏服务的, 这个服务就是 StatusbarManagerService (简称:SMS),和这个服务关系比较密切的服务是 WindowManagerService(简称:WMS), SMS 主要管控的是状态栏、导航栏, 例如:我们可以设置全屏、沉浸式状态栏都是 SMS 在起作用。
services组件启动时配置列表 : (R.array.config_systemUIServiceComponents)
所有 SystemUIService 都是继承自 SystemUI.class , SystemUI.class 是一个抽象类
<item>com.android.systemui.util.NotificationChannels</item> 通知信息 <item>com.android.systemui.keyguard.KeyguardViewMediator</item> 锁屏 <item>com.android.systemui.recents.Recents</item> 近期列表 Android 10之后近期列表的显示被移到Launcher里面了。在Launcher3的一个 类中TouchInteractionService.java IBinder mMyBinder = new IOverviewProxy.Stub() 通过AIDL的方法与systemUI通信 ———————————————— <item>com.android.systemui.volume.VolumeUI</item> 声音UI显示 <item>com.android.systemui.statusbar.phone.StatusBar</item> 状态栏及下拉面板 <item>com.android.systemui.usb.StorageNotification</item> usb通知管理 <item>com.android.systemui.power.PowerUI</item> 电源UI显示管理 <item>com.android.systemui.media.RingtonePlayer</item> 播放铃声 <item>com.android.systemui.keyboard.KeyboardUI</item>键盘UI <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>快捷方式 <item>@string/config_systemUIVendorServiceComponent</item>厂商相关定制 <item>com.android.systemui.util.leak.GarbageMonitor$Service</item>垃圾监测器 <item>com.android.systemui.LatencyTester</item> 延迟测试仪 <item>com.android.systemui.globalactions.GlobalActionsComponent</item> 关机界面的显示、全局控制 <item>com.android.systemui.ScreenDecorations</item>屏幕装饰 <item>com.android.systemui.biometrics.AuthController</item>生物识别 <item>com.android.systemui.SliceBroadcastRelayHandler</item> 切片广播 <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item> <item>com.android.systemui.theme.ThemeOverlayController</item> <item>com.android.systemui.accessibility.WindowMagnification</item> <item>com.android.systemui.accessibility.SystemActions</item> <item>com.android.systemui.toast.ToastUI</item> Toast <item>com.android.systemui.wmshell.WMShell</item>
一、SystemUI的启动流程
1、SystemServer
SystemServer启动后,会在Main Thread启动ActivityManagerService,当ActivityManagerService systemReady后,会去启动SystemUIService。
/frameworks/base/services/java/com/android/server/SystemServer.java
①main
/** * The main entry point from zygote. */ public static void main(String[] args) { new SystemServer().run(); }
②run
private void run() { t.traceBegin("InitBeforeStartServices"); .... // Create the system service manager. mSystemServiceManager = new SystemServiceManager(mSystemContext); mSystemServiceManager.setStartInfo(mRuntimeRestart, mRuntimeStartElapsedTime, mRuntimeStartUptime); LocalServices.addService(SystemServiceManager.class, mSystemServiceManager); .... }
③mActivityManagerService.systemReady
mActivityManagerService.systemReady(() -> { //准备好服务 Slog.i(TAG, "Making services ready"); .... //跟踪开启系统界面 t.traceBegin("StartSystemUI"); try { //开启系统界面 startSystemUi(context, windowManagerF); } catch (Throwable e) { reportWtf("starting System UI", e); } t.traceEnd(); .... }
④startSystemUi
private static void startSystemUi(Context context, WindowManagerService windowManager) { PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class); Intent intent = new Intent(); intent.setComponent(pm.getSystemUiServiceComponent()); intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING); //Slog.d(TAG, "Starting service: " + intent); //通过startServiceAsUser,SystemUIService就启动了,即SystemUI进程开机启动 context.startServiceAsUser(intent, UserHandle.SYSTEM); windowManager.onSystemUiStarted(); }
2、systemUIService
在SystemUIService的onCreate方法中会调用SystemUIApplication的startServicesIfNeeded方法,这个方法会调用 startServicesIfNeeded(SERVICES)方法启动一系列服务
@Override public void onCreate() { super.onCreate(); // Start all of SystemUI ((SystemUIApplication) getApplication()).startServicesIfNeeded();
3、SystemUIApplication
/frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
①startServicesIfNeeded()
public void startServicesIfNeeded() { //获取所有的服务的路径,所有SERVICES统一继承了SystemUI类: String[] names = SystemUIFactory.getInstance().getSystemUIServiceComponents(getResources()); startServicesIfNeeded(/* metricsPrefix= */ "StartServices", names); }
②startServicesIfNeeded(String metricsPrefix, String[] services)
在重载方法中将每一个名称通过反射来得到实例对象,然后依次调用每一个SystemUI的子类的start方法启动每一个模块。
private void startServicesIfNeeded(String metricsPrefix, String[] services) { if (mServicesStarted) { return; } mServices = new SystemUI[services.length]; //检查一下,也许在我们开始之前很久它就已经完成了 if (!mBootCompleteCache.isBootComplete()) { // check to see if maybe it was already completed long before we began // see ActivityManagerService.finishBooting() if ("1".equals(SystemProperties.get("sys.boot_completed"))) { mBootCompleteCache.setBootComplete(); if (DEBUG) { Log.v(TAG, "BOOT_COMPLETED was already sent"); } } } final DumpManager dumpManager = mRootComponent.createDumpManager(); Log.v(TAG, "Starting SystemUI services for user " + Process.myUserHandle().getIdentifier() + "."); TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming", Trace.TRACE_TAG_APP); //开始追踪 log.traceBegin(metricsPrefix); final int N = services.length; //遍历services这个数组 for (int i = 0; i < N; i++) { String clsName = services[i]; if (DEBUG) Log.d(TAG, "loading: " + clsName); log.traceBegin(metricsPrefix + clsName); long ti = System.currentTimeMillis(); try { SystemUI obj = mComponentHelper.resolveSystemUI(clsName); if (obj == null) { Constructor constructor = Class.forName(clsName).getConstructor(Context.class); obj = (SystemUI) constructor.newInstance(this); } mServices[i] = obj; } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException ex) { throw new RuntimeException(ex); } if (DEBUG) Log.d(TAG, "running: " + mServices[i]); //依次调用service的start方法启动服务 mServices[i].start(); log.traceEnd(); // Warn if initialization of component takes too long //如果组件初始化时间过长,则发出警告 ti = System.currentTimeMillis() - ti; if (ti > 1000) { Log.w(TAG, "Initialization of " + clsName + " took " + ti + " ms"); } if (mBootCompleteCache.isBootComplete()) { mServices[i].onBootCompleted(); } dumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]); } mRootComponent.getInitController().executePostInitTasks(); //结束追踪 log.traceEnd(); mServicesStarted = true; }
4、SystemUI
/** * @see SystemUIApplication#startServicesIfNeeded() *系统界面应用 如果需要,启动服务 */ public abstract class SystemUI implements Dumpable { protected final Context mContext; public SystemUI(Context context) { mContext = context; } public abstract void start(); protected void onConfigurationChanged(Configuration newConfig) { } @Override public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { } protected void onBootCompleted() { } public static void overrideNotificationAppName(Context context, Notification.Builder n, boolean system) { final Bundle extras = new Bundle(); String appName = system ? context.getString(com.android.internal.R.string.notification_app_name_system) : context.getString(com.android.internal.R.string.notification_app_name_settings); extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, appName); n.addExtras(extras); } }
二、状态栏
1、SystemBars
SystemBars加载基本全部SystemUI的界面显示,由前面可知调用的start方法实际上是每一个继承于SytemUI的子类中的方法
①start
②createStatusBarFromConfig
从string资源文件里面读取class name,通过java的反射机制实例化对象,然后调用start()方法启动,class name的值如下图:
private void createStatusBarFromConfig() { ...... String clsName = mContext.getString(R.string.config_statusBarComponent); ...... try { cls = mContext.getClassLoader().loadClass(clsName); } catch (Throwable t) { throw andLog("Error loading status bar component: " + clsName, t); } try { mStatusBar = (BaseStatusBar) cls.newInstance(); } catch (Throwable t) { throw andLog("Error creating status bar component: " + clsName, t); } ...... mStatusBar.start(); ...... }
③String
<!-- Component to be used as the status bar service. Must implement the IStatusBar interface. This name is in the ComponentName flattened format (package/class) --> <string name="config_statusBarComponent" translatable="false">com.android.systemui.statusbar.phone.StatusBar</string>
2、StatusBar
①createAndAddWindows
public void createAndAddWindows(@Nullable RegisterStatusBarResult result) { //创建状态栏 makeStatusBarView(result); mNotificationShadeWindowController.attach(); //创建状态栏的窗口 mStatusBarWindowController.attach(); }
②makeStatusBarView
protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) { final Context context = mContext; ..... FragmentHostManager.get(mPhoneStatusBarWindow) .addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> { //CollapsedStatusBarFragment 替换 status_bar_container(状态栏通知显示区域) CollapsedStatusBarFragment statusBarFragment = (CollapsedStatusBarFragment) fragment; PhoneStatusBarView oldStatusBarView = mStatusBarView; mStatusBarView = (PhoneStatusBarView) statusBarFragment.getView(); //传递statusBar处理下拉事件 mStatusBarView.setBar(this); //传递 NotificationPanelView 显示下拉UI控制 mStatusBarView.setPanel(mNotificationPanelViewController); mStatusBarView.setScrimController(mScrimController); //初始化通知栏区域 statusBarFragment.initNotificationIconArea(mNotificationIconAreaController); ...... }).getFragmentManager() .beginTransaction() .replace(R.id.status_bar_container, new CollapsedStatusBarFragment(), CollapsedStatusBarFragment.TAG) .commit(); .....
2、CollapsedStatusBarFragment
①onCreateView
@Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.status_bar, container, false); }
②initNotificationIconArea
public void initNotificationIconArea(NotificationIconAreaController notificationIconAreaController) { //notification_icon_area是在status_bar.xml的布局,它是属于通知Notification //获取到 notification_icon_area,FrameLayout转为ViewGroup, ViewGroup notificationIconArea = mStatusBar.findViewById(R.id.notification_icon_area); //调用 notificationIconAreaController 获取通知要显示的view(LinearLayout) //在4中跟进 mNotificationIconAreaInner = notificationIconAreaController.getNotificationInnerAreaView(); //如果已经有显示的view,通过 view 父布局将其自身remove,然后再重新addView。 //最后将 mNotificationIconAreaInner 显示出来(设置透明度为1,visibility为VISIBLE) if (mNotificationIconAreaInner.getParent() != null) { ((ViewGroup) mNotificationIconAreaInner.getParent()) .removeView(mNotificationIconAreaInner); } notificationIconArea.addView(mNotificationIconAreaInner); //与上面一样 ViewGroup statusBarCenteredIconArea = mStatusBar.findViewById(R.id.centered_icon_area); mCenteredIconArea = notificationIconAreaController.getCenteredNotificationAreaView(); if (mCenteredIconArea.getParent() != null) { ((ViewGroup) mCenteredIconArea.getParent()) .removeView(mCenteredIconArea); } statusBarCenteredIconArea.addView(mCenteredIconArea); //默认为显示,直到我们知道其他情况 // Default to showing until we know otherwise. showNotificationIconArea(false); }
③showNotificationIconArea
当状态栏下拉时,状态栏中的图标icon会慢慢的变成透明和不可见,就是通过hideSystemIconArea(true), hideNotificationIconArea(true)
//当状态栏下拉时,设置状态栏中的图标icon会慢慢的变成透明和不可见 public void hideNotificationIconArea(boolean animate) { animateHide(mNotificationIconAreaInner, animate); animateHide(mCenteredIconArea, animate); } //设置状态栏图标透明度为1,visibility为VISIBLE public void showNotificationIconArea(boolean animate) { animateShow(mNotificationIconAreaInner, animate); animateShow(mCenteredIconArea, animate); } public void hideOperatorName(boolean animate) { if (mOperatorNameFrame != null) { animateHide(mOperatorNameFrame, animate); } } public void showOperatorName(boolean animate) { if (mOperatorNameFrame != null) { animateShow(mOperatorNameFrame, animate); }
④animateShow
private void animateShow(View v, boolean animate) { v.animate().cancel(); //(设置透明度为1,visibility为VISIBLE) v.setVisibility(View.VISIBLE); if (!animate) { v.setAlpha(1f); return; } ..... }
⑤animateHiddenState
//将视图动画化为 INVISIBLE 或 GONE private void animateHiddenState(final View v, int state, boolean animate) { v.animate().cancel(); if (!animate) { v.setAlpha(0f); v.setVisibility(state); return; } v.animate() .alpha(0f) .setDuration(160) .setStartDelay(0) .setInterpolator(Interpolators.ALPHA_OUT) .withEndAction(() -> v.setVisibility(state)); }
3、status_bar.xml
<?xml version="1.0" encoding="utf-8"?> <!-- ** ** Copyright 2006, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ --> <!-- android:background="@drawable/status_bar_closed_default_background" --> <com.android.systemui.statusbar.phone.PhoneStatusBarView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui" android:layout_width="match_parent" android:layout_height="@dimen/status_bar_height" android:id="@+id/status_bar" android:orientation="vertical" android:focusable="false" android:descendantFocusability="afterDescendants" android:accessibilityPaneTitle="@string/status_bar" > <!-- add for KGDAANWIKFRA-135 --> <View android:layout_width="match_parent" android:layout_height="@dimen/status_bar_height" android:id="@+id/status_bar_dark_view" android:background="#ff000000" android:visibility="gone" /> //<!--通知灯,默认gone--> <ImageView android:id="@+id/notification_lights_out" android:layout_width="@dimen/status_bar_icon_size" android:layout_height="match_parent" android:paddingStart="@dimen/status_bar_padding_start" android:paddingBottom="2dip" android:src="@drawable/ic_sysbar_lights_out_dot_small" android:scaleType="center" android:visibility="gone" /> //<!--状态栏内容--> <LinearLayout android:id="@+id/status_bar_contents" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingStart="@dimen/status_bar_padding_start" android:paddingEnd="@dimen/status_bar_padding_end" android:paddingTop="@dimen/status_bar_padding_top" android:orientation="horizontal" > <FrameLayout android:layout_height="match_parent" android:layout_width="0dp" android:layout_weight="1"> <include layout="@layout/heads_up_status_bar_layout" /> <!-- The alpha of the left side is controlled by PhoneStatusBarTransitions, and the individual views are controlled by StatusBarManager disable flags DISABLE_CLOCK and DISABLE_NOTIFICATION_ICONS, respectively --> <LinearLayout android:id="@+id/status_bar_left_side" android:layout_height="match_parent" android:layout_width="match_parent" android:clipChildren="false" > <ViewStub android:id="@+id/operator_name" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout="@layout/operator_name" /> <com.android.systemui.statusbar.policy.Clock android:id="@+id/clock" android:layout_width="wrap_content" android:layout_height="match_parent" android:textAppearance="@style/TextAppearance.StatusBar.Clock" android:singleLine="true" android:paddingStart="@dimen/status_bar_left_clock_starting_padding" android:paddingEnd="@dimen/status_bar_left_clock_end_padding" android:gravity="center_vertical|start" /> //<!--通知图标区域--> <com.android.systemui.statusbar.AlphaOptimizedFrameLayout android:id="@+id/notification_icon_area" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:orientation="horizontal" android:clipChildren="false"/> </LinearLayout> </FrameLayout> <!-- Space should cover the notch (if it exists) and let other views lay out around it --> <android.widget.Space android:id="@+id/cutout_space_view" android:layout_width="0dp" android:layout_height="match_parent" android:gravity="center_horizontal|center_vertical" /> //居中的图标区域 <com.android.systemui.statusbar.AlphaOptimizedFrameLayout android:id="@+id/centered_icon_area" android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="horizontal" android:clipChildren="false" android:gravity="center_horizontal|center_vertical"/> <com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:orientation="horizontal" android:gravity="center_vertical|end" > //<!--系统图标--> <include layout="@layout/system_icons" /> </com.android.keyguard.AlphaOptimizedLinearLayout> </LinearLayout> //<!--紧急密码管理员文本--> <ViewStub android:id="@+id/emergency_cryptkeeper_text" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout="@layout/emergency_cryptkeeper_text" /> </com.android.systemui.statusbar.phone.PhoneStatusBarView>
4、NotificationIconAreaController
①getNotificationInnerAreaView
/** * Returns the view that represents the notification area.+ * 返回表示通知区域的视图。 */ public View getNotificationInnerAreaView() { return mNotificationIconArea; }
②initializeNotificationAreaViews
/** * Initializes the views that will represent the notification area. * 初始化将表示通知区域的视图。 */ protected void initializeNotificationAreaViews(Context context) { reloadDimens(context); LayoutInflater layoutInflater = LayoutInflater.from(context); //通知图标区域布局 mNotificationIconArea = inflateIconArea(layoutInflater); //通知图标 mNotificationIcons = mNotificationIconArea.findViewById(R.id.notificationIcons); //获取通知滚动布局 mNotificationScrollLayout = mStatusBar.getNotificationScrollLayout(); //中心图标区域布局 mCenteredIconArea = layoutInflater.inflate(R.layout.center_icon_area, null); //居中的图标 mCenteredIcon = mCenteredIconArea.findViewById(R.id.centeredIcon); initAodIcons(); }
③inflateIconArea
protected View inflateIconArea(LayoutInflater inflater) { return inflater.inflate(R.layout.notification_icon_area, null); }
三、status icon加载流程
1、 status_bar.xml
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout android:id="@+id/centered_icon_area" android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="horizontal" android:clipChildren="false" android:gravity="center_horizontal|center_vertical"/> <com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:orientation="horizontal" android:gravity="center_vertical|end" > <!--系统图标--> <include layout="@layout/system_icons" /> </com.android.keyguard.AlphaOptimizedLinearLayout>
2、system_icons.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:systemui="http://schemas.android.com/apk/res-auto" android:id="@+id/system_icons" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_vertical"> //StatusIconContainer继承AlphaOptimizedLinearLayout <com.android.systemui.statusbar.phone.StatusIconContainer android:id="@+id/statusIcons" android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent" android:paddingEnd="@dimen/signal_cluster_battery_padding" android:gravity="center_vertical" android:orientation="horizontal"/> <com.android.systemui.BatteryMeterView android:id="@+id/battery" android:layout_height="match_parent" android:layout_width="wrap_content" android:clipToPadding="false" android:clipChildren="false" systemui:textAppearance="@style/TextAppearance.StatusBar.Clock" /> </LinearLayout>
3、AlphaOptimizedLinearLayout
//该方法用来标记当前view是否存在过度绘制,存在返回ture,不存在返回false, //api里面默认返回为true,status icon不存在过度绘制。 @Override public boolean hasOverlappingRendering() { return false; }
4、CollapsedStatusBarFragment
@Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mStatusBar = (PhoneStatusBarView) view; if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_PANEL_STATE)) { mStatusBar.restoreHierarchyState( savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE)); } ...... }
4、StatusBarIconController
①DarkIconManager
/** * Version of ViewGroup that observes state from the DarkIconDispatcher. */ public static class DarkIconManager extends IconManager { private final DarkIconDispatcher mDarkIconDispatcher; private int mIconHPadding; public DarkIconManager(LinearLayout linearLayout, CommandQueue commandQueue) { super(linearLayout, commandQueue); mIconHPadding = mContext.getResources().getDimensionPixelSize( R.dimen.status_bar_icon_padding); mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class); } //每个icon应该就是对应着代表顺序的index和数据类型为String的slot @Override protected void onIconAdded(int index, String slot, boolean blocked, StatusBarIconHolder holder) { StatusIconDisplayable view = addHolder(index, slot, blocked, holder); mDarkIconDispatcher.addDarkReceiver((DarkReceiver) view); } ..... //onSetIcon可能就是刷新icon状态的 @Override public void onSetIcon(int viewIndex, StatusBarIcon icon) { super.onSetIcon(viewIndex, icon); mDarkIconDispatcher.applyDark((DarkReceiver) mGroup.getChildAt(viewIndex)); } ..... }
②IconManager
public static class IconManager implements DemoMode { ..... protected void onIconAdded(int index, String slot, boolean blocked, StatusBarIconHolder holder) { addHolder(index, slot, blocked, holder); } protected StatusIconDisplayable addHolder(int index, String slot, boolean blocked, StatusBarIconHolder holder) { switch (holder.getType()) { case TYPE_ICON: return addIcon(index, slot, blocked, holder.getIcon()); case TYPE_WIFI: return addSignalIcon(index, slot, holder.getWifiState()); case TYPE_MOBILE: return addMobileIcon(index, slot, holder.getMobileState()); } return null; } @VisibleForTesting protected StatusBarIconView addIcon(int index, String slot, boolean blocked, StatusBarIcon icon) { StatusBarIconView view = onCreateStatusBarIconView(slot, blocked); view.set(icon); mGroup.addView(view, index, onCreateLayoutParams()); return view; } ..... }
5、StatusBarIconControllerImpl
//继承StatusBarIconList public class StatusBarIconControllerImpl extends StatusBarIconList implements Tunable, ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController { ...... @Inject public StatusBarIconControllerImpl(Context context, CommandQueue commandQueue) { //config_statusBarIcons super(context.getResources().getStringArray( com.android.internal.R.array.config_statusBarIcons)); Dependency.get(ConfigurationController.class).addCallback(this); ..... } }
6、StatusBarIconList
在初始化的时候就已经定义好了所有的slots,然后从framework中加载出来,index就是string-array中的顺序。
public class StatusBarIconList { private ArrayList<Slot> mSlots = new ArrayList<>(); public StatusBarIconList(String[] slots) { final int N = slots.length; for (int i=0; i < N; i++) { mSlots.add(new Slot(slots[i], null)); } }
7、config_statusBarIcons
<string-array name="config_statusBarIcons"> <item><xliff:g id="id">@string/status_bar_rotate</xliff:g></item> <item><xliff:g id="id">@string/status_bar_headset</xliff:g></item> <item><xliff:g id="id">@string/status_bar_data_saver</xliff:g></item> <item><xliff:g id="id">@string/status_bar_managed_profile</xliff:g></item> <item><xliff:g id="id">@string/status_bar_ime</xliff:g></item> <item><xliff:g id="id">@string/status_bar_sync_failing</xliff:g></item> <item><xliff:g id="id">@string/status_bar_sync_active</xliff:g></item> <item><xliff:g id="id">@string/status_bar_cast</xliff:g></item> <item><xliff:g id="id">@string/status_bar_hotspot</xliff:g></item> <item><xliff:g id="id">@string/status_bar_location</xliff:g></item> <item><xliff:g id="id">@string/status_bar_bluetooth</xliff:g></item> <item><xliff:g id="id">@string/status_bar_nfc</xliff:g></item> <item><xliff:g id="id">@string/status_bar_tty</xliff:g></item> <item><xliff:g id="id">@string/status_bar_speakerphone</xliff:g></item> <item><xliff:g id="id">@string/status_bar_zen</xliff:g></item> <item><xliff:g id="id">@string/status_bar_mute</xliff:g></item> <item><xliff:g id="id">@string/status_bar_volume</xliff:g></item> <item><xliff:g id="id">@string/status_bar_vpn</xliff:g></item> <item><xliff:g id="id">@string/status_bar_ethernet</xliff:g></item> <item><xliff:g id="id">@string/status_bar_wifi</xliff:g></item> <item><xliff:g id="id">@string/status_bar_mobile</xliff:g></item> <item><xliff:g id="id">@string/status_bar_airplane</xliff:g></item> <item><xliff:g id="id">@string/status_bar_cdma_eri</xliff:g></item> <item><xliff:g id="id">@string/status_bar_data_connection</xliff:g></item> <item><xliff:g id="id">@string/status_bar_phone_evdo_signal</xliff:g></item> <item><xliff:g id="id">@string/status_bar_phone_signal</xliff:g></item> <item><xliff:g id="id">@string/status_bar_battery</xliff:g></item> <item><xliff:g id="id">@string/status_bar_alarm_clock</xliff:g></item> <item><xliff:g id="id">@string/status_bar_secure</xliff:g></item> <item><xliff:g id="id">@string/status_bar_clock</xliff:g></item> </string-array> <string translatable="false" name="status_bar_rotate">rotate</string> <string translatable="false" name="status_bar_headset">headset</string> <string translatable="false" name="status_bar_data_saver">data_saver</string> <string translatable="false" name="status_bar_managed_profile">managed_profile</string> <string translatable="false" name="status_bar_ime">ime</string> <string translatable="false" name="status_bar_sync_failing">sync_failing</string> <string translatable="false" name="status_bar_sync_active">sync_active</string> <string translatable="false" name="status_bar_cast">cast</string> <string translatable="false" name="status_bar_hotspot">hotspot</string> <string translatable="false" name="status_bar_location">location</string> <string translatable="false" name="status_bar_bluetooth">bluetooth</string> <string translatable="false" name="status_bar_nfc">nfc</string> <string translatable="false" name="status_bar_tty">tty</string> <string translatable="false" name="status_bar_speakerphone">speakerphone</string> <string translatable="false" name="status_bar_zen">zen</string> <string translatable="false" name="status_bar_mute">mute</string> <string translatable="false" name="status_bar_volume">volume</string> <string translatable="false" name="status_bar_wifi">wifi</string> <string translatable="false" name="status_bar_cdma_eri">cdma_eri</string> <string translatable="false" name="status_bar_data_connection">data_connection</string> <string translatable="false" name="status_bar_phone_evdo_signal">phone_evdo_signal</string> <string translatable="false" name="status_bar_phone_signal">phone_signal</string> <string translatable="false" name="status_bar_battery">battery</string> <string translatable="false" name="status_bar_alarm_clock">alarm_clock</string> <string translatable="false" name="status_bar_secure">secure</string> <string translatable="false" name="status_bar_clock">clock</string> <string translatable="false" name="status_bar_mobile">mobile</string> <string translatable="false" name="status_bar_vpn">vpn</string> <string translatable="false" name="status_bar_ethernet">ethernet</string> <string translatable="false" name="status_bar_airplane">airplane</string>
好了,到这里我们的第一部分初始化流程就讲完了
四、状态显示流程
由上面的初始化流程我们可以知道,每个icon都对应了slot,slot数量比较多,我们就挑一个常见的Headset讲下,其他的流程都是大致一样的。
1、PhoneStatusBarPolicy
初始化注册了大量的监听
①init
// 初始化headset的slot mSlotHeadset = resources.getString(com.android.internal.R.string.status_bar_headset); /** Initialize the object after construction. */ public void init() { // listen for broadcasts IntentFilter filter = new IntentFilter(); // 注册headset状态变化的action filter.addAction(AudioManager.ACTION_HEADSET_PLUG); filter.addAction(Intent.ACTION_SIM_STATE_CHANGED); filter.addAction(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED); mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, mHandler); Observer<Integer> observer = ringer -> mHandler.post(this::updateVolumeZen); mRingerModeTracker.getRingerMode().observeForever(observer); mRingerModeTracker.getRingerModeInternal().observeForever(observer); .... }
②BroadcastReceiver
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); switch (action) { case Intent.ACTION_SIM_STATE_CHANGED: // Avoid rebroadcast because SysUI is direct boot aware. if (intent.getBooleanExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, false)) { break; } break; case TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED: updateTTY(intent.getIntExtra(TelecomManager.EXTRA_CURRENT_TTY_MODE, TelecomManager.TTY_MODE_OFF)); break; case Intent.ACTION_MANAGED_PROFILE_AVAILABLE: case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE: case Intent.ACTION_MANAGED_PROFILE_REMOVED: updateManagedProfile(); break; //监听ACTION_HEADSET_PLUG case AudioManager.ACTION_HEADSET_PLUG: updateHeadsetPlug(context, intent); break; } } };
③updateHeadsetPlug
完成icon添加和状态监听,然后当收到对应的action变化的时候,更新headset icon
private void updateHeadsetPlug(Context context, Intent intent) { boolean connected = intent.getIntExtra("state", 0) != 0; boolean hasMic = intent.getIntExtra("microphone", 0) != 0; if (connected) { String contentDescription = mResources.getString(hasMic ? R.string.accessibility_status_bar_headset : R.string.accessibility_status_bar_headphones); //setIcon负责设置icon mIconController.setIcon(mSlotHeadset, hasMic ? R.drawable.stat_sys_headset_mic : R.drawable.stat_sys_headset, contentDescription); //setIconVisibility则根据connected状态设置icon的可见性 mIconController.setIconVisibility(mSlotHeadset, true); } else { /*UNISOC: Add for bug 1130932 {@ */ AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); if (!audioManager.isWiredHeadsetOn()) { //setIconVisibility则根据connected状态设置icon的可见性 mIconController.setIconVisibility(mSlotHeadset, false); } /* @} */ } }
2、StatusBarIconControllerImpl
①setIcon
@Override public void setIcon(String slot, int resourceId, CharSequence contentDescription) { //根据slot找到对应的index int index = getSlotIndex(slot); //用index取得对应的icon StatusBarIconHolder holder = getIcon(index, 0); if (holder == null) { //第一次的时候,icon == null,所以通过new StatusBarIcon创建一个 StatusBarIcon icon = new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(), Icon.createWithResource( mContext, resourceId), 0, 0, contentDescription); holder = StatusBarIconHolder.fromIcon(icon); //然后调用此方法 setIcon(index, holder); } else { holder.getIcon().icon = Icon.createWithResource(mContext, resourceId); holder.getIcon().contentDescription = contentDescription; handleSet(index, holder); } } @Override public void setIcon(int index, @NonNull StatusBarIconHolder holder) { boolean isNew = getIcon(index, holder.getTag()) == null; super.setIcon(index, holder); if (isNew) { addSystemIcon(index, holder); } else { handleSet(index, holder); } } private void addSystemIcon(int index, StatusBarIconHolder holder) { String slot = getSlotName(index); int viewIndex = getViewIndex(index, holder.getTag()); boolean blocked = mIconBlacklist.contains(slot); //onIconAdded-》初始化流程里面StatusBarIconController中添加icon的入口 //到这里setIcon就添加完毕了 mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, blocked, holder)); }
②setIconVisibility
public void setIconVisibility(String slot, boolean visibility) { int index = getSlotIndex(slot); StatusBarIconHolder holder = getIcon(index, 0); if (holder == null || holder.isVisible() == visibility) { return; } holder.setVisible(visibility); //icon已经创建成功了,icon非空,并且第一次是icon.visible != visibility的 //就会顺利的走到handleSet(index, icon) handleSet(index, holder); } private void handleSet(int index, StatusBarIconHolder holder) { int viewIndex = getViewIndex(index, holder.getTag()); //初始化流程里面StatusBarIconController中setIcon的地方 //到这里setIconVisibility也设置完毕了 mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder)); }
五、创建状态栏的窗口
1、StatusBarWindowController
mStatusBarWindowController.attach()
/** * Adds the status bar view to the window manager. */ public void attach() { // Now that the status bar window encompasses the sliding panel and its // translucent backdrop, the entire thing is made TRANSLUCENT and is // hardware-accelerated. mLp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, mBarHeight, WindowManager.LayoutParams.TYPE_STATUS_BAR, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, PixelFormat.TRANSLUCENT); mLp.privateFlags |= PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC; mLp.token = new Binder(); mLp.gravity = Gravity.TOP; mLp.setFitInsetsTypes(0 /* types */); mLp.setTitle("StatusBar"); mLp.packageName = mContext.getPackageName(); mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; //WindowManager中添加view //mStatusBarView = mSuperStatusBarViewFactory.getStatusBarWindowView(); //private final SuperStatusBarViewFactory mSuperStatusBarViewFactory; mWindowManager.addView(mStatusBarView, mLp); mLpChanged.copyFrom(mLp); }
2、SuperStatusBarViewFactory
/** * Gets the inflated {@link StatusBarWindowView} from {@link R.layout#super_status_bar}. * Returns a cached instance, if it has already been inflated. */ public StatusBarWindowView getStatusBarWindowView() { if (mStatusBarWindowView != null) { return mStatusBarWindowView; } //由其可知加载的布局来自于super_status_bar mStatusBarWindowView = (StatusBarWindowView) mInjectionInflationController.injectable( LayoutInflater.from(mContext)).inflate(R.layout.super_status_bar, /* root= */ null); if (mStatusBarWindowView == null) { throw new IllegalStateException( "R.layout.super_status_bar could not be properly inflated"); } return mStatusBarWindowView; }
3、super_status_bar.xml
从前面可知这里会用CollapsedStatusBarFragment 替换 status_bar_container(状态栏通知显示区域),完成图标的显示
<!-- This is the status bar window. --> <com.android.systemui.statusbar.phone.StatusBarWindowView 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"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > <FrameLayout android:id="@+id/status_bar_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:visibility="gone" /> <FrameLayout android:id="@+id/car_top_navigation_bar_container" android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout> </com.android.systemui.statusbar.phone.StatusBarWindowView
六、电池图标刷新流程
1、BatteryMeterView
① 构造方法BatteryMeterView()
public BatteryMeterView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); BroadcastDispatcher broadcastDispatcher = Dependency.get(BroadcastDispatcher.class); setOrientation(LinearLayout.HORIZONTAL); setGravity(Gravity.CENTER_VERTICAL | Gravity.START); TypedArray atts = context.obtainStyledAttributes(attrs, R.styleable.BatteryMeterView, defStyle, 0); final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor, context.getColor(R.color.meter_background_color)); mPercentageStyleId = atts.getResourceId(R.styleable.BatteryMeterView_textAppearance, 0); /*Bug 1296708 add charge animation of batteryView*/ //添加电池视图的充电动画 true mBatteryAnimation = mContext.getResources().getBoolean( R.bool.config_battery_animation); //将电池等级添加到父布局中 if (mBatteryAnimation) { //mBatteryAnimation为true mUnisocDrawable = new BatteryMeterDrawable(context, new Handler(), frameColor, false); }else{ mDrawable = new ThemedBatteryDrawable(context, frameColor); } /*@}*/ atts.recycle(); mSettingObserver = new SettingObserver(new Handler(context.getMainLooper())); mShowPercentAvailable = context.getResources().getBoolean( com.android.internal.R.bool.config_battery_percentage_setting_available); addOnAttachStateChangeListener( new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS, Dependency.get(CommandQueue.class))); setupLayoutTransition(); mSlotBattery = context.getString( com.android.internal.R.string.status_bar_battery); mBatteryIconView = new ImageView(context); /*Bug 1296708 add charge animation of batteryView*/ //添加电池视图的充电动画 if (mBatteryAnimation) { mBatteryIconView.setImageDrawable(mUnisocDrawable); }else{ mBatteryIconView.setImageDrawable(mDrawable); } final MarginLayoutParams mlp = new MarginLayoutParams( getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_width), getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_height)); mlp.setMargins(0, 0, 0, getResources().getDimensionPixelOffset(R.dimen.battery_margin_bottom)); addView(mBatteryIconView, mlp); updateShowPercent(); mDualToneHandler = new DualToneHandler(context); // Init to not dark at all. //设置默认的电池布局的主题色,当状态栏主题发生改变时,电池布局会做相应的更换(亮色和暗色切换) //在 PhoneStatusBarView 中添加了DarkReceiver监听,最终调用到 BatteryMeterView 的onDarkChanged()方法 onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT); //设置 Settings.System.SHOW_BATTERY_PERCENT 监听 mUserTracker = new CurrentUserTracker(broadcastDispatcher) { @Override public void onUserSwitched(int newUserId) { mUser = newUserId; getContext().getContentResolver().unregisterContentObserver(mSettingObserver); getContext().getContentResolver().registerContentObserver( Settings.System.getUriFor(SHOW_BATTERY_PERCENT), false, mSettingObserver, newUserId); //当用户点击了显示电量百分比开关,则调用 updateShowPercent()方法在电池等级前添加电量百分比 updateShowPercent(); } }; setClipChildren(false); setClipToPadding(false); Dependency.get(ConfigurationController.class).observe(viewAttachLifecycle(this), this); }
②onDarkChanged
//修改百分比的字体颜色和电池等级的画笔颜色和背景颜色 @Override public void onDarkChanged(Rect area, float darkIntensity, int tint) { float intensity = DarkIconDispatcher.isInArea(area, this) ? darkIntensity : 0; mNonAdaptedSingleToneColor = mDualToneHandler.getSingleColor(intensity); mNonAdaptedForegroundColor = mDualToneHandler.getFillColor(intensity); mNonAdaptedBackgroundColor = mDualToneHandler.getBackgroundColor(intensity); if (!mUseWallpaperTextColors) { //添加电池充电动画 updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor, mNonAdaptedSingleToneColor); } }
③updateColors
//在电池等级前添加电量百分比 private void updateColors(int foregroundColor, int backgroundColor, int singleToneColor) { /*Bug 1296708 add charge animation of batteryView*/ //添加电池充电动画 if (mDrawable != null) { mDrawable.setColors(foregroundColor, backgroundColor, singleToneColor); } if (mUnisocDrawable != null) { mUnisocDrawable.setColors(foregroundColor, backgroundColor); } mTextColor = singleToneColor; if (mBatteryPercentView != null) { mBatteryPercentView.setTextColor(singleToneColor); } }
2、PhoneStatusBarView
private DarkReceiver mBattery; @Override public void onFinishInflate() { // mBattery = findViewById(R.id.battery); mCutoutSpace = findViewById(R.id.cutout_space_view); mCenterIconSpace = findViewById(R.id.centered_icon_area); updateResources(); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); // Always have Battery meters in the status bar observe the dark/light modes. //始终在状态栏的电池仪表观察暗/光模式。 Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mBattery); if (updateOrientationAndCutout()) { updateLayoutForCutout(); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(mBattery); mDisplayCutout = null; }
七、电池状态改变流程
1、BatteryControllerImpl
①init
@Override public void init() { //注册广播 registerReceiver(); if (!mHasReceivedBattery) { // Get initial state. Relying on Sticky behavior until API for getting info. Intent intent = mContext.registerReceiver( null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED) ); if (intent != null && !mHasReceivedBattery) { onReceive(mContext, intent); } } updatePowerSave(); updateEstimate(); }
②registerReceiver
private void registerReceiver() { IntentFilter filter = new IntentFilter(); //添加广播的方式接收 filter.addAction(Intent.ACTION_BATTERY_CHANGED); filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); /* UNISOC: Bug 1363779 battery icon shows '+' after switching from power saving mode to super power saving @{ */ filter.addAction(UnisocPowerManagerUtil.ACTION_POWEREX_SAVE_MODE_CHANGED); /* @} */ filter.addAction(ACTION_LEVEL_TEST); mBroadcastDispatcher.registerReceiver(this, filter); }
③onReceive
@Override public void onReceive(final Context context, Intent intent) { final String action = intent.getAction(); //监听到ACTION_BATTERY_CHANGED if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { if (mTestmode && !intent.getBooleanExtra("testmode", false)) return; mHasReceivedBattery = true; mLevel = (int)(100f * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0) / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100)); mPluggedIn = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0; final int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN); mCharged = status == BatteryManager.BATTERY_STATUS_FULL; mCharging = mCharged || status == BatteryManager.BATTERY_STATUS_CHARGING; //遍历回调监听,将状态参数发送 fireBatteryLevelChanged(); } .... }
④fireBatteryLevelChanged
protected final ArrayList<BatteryController.BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>(); protected void fireBatteryLevelChanged() { synchronized (mChangeCallbacks) { final int N = mChangeCallbacks.size(); //遍历回调监听,将状态参数发送 for (int i = 0; i < N; i++) { mChangeCallbacks.get(i).onBatteryLevelChanged(mLevel, mPluggedIn, mCharging); } } }
2、BatteryStateChangeCallback
interface BatteryStateChangeCallback { default void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { } default void onPowerSaveChanged(boolean isPowerSave) { } default void onReverseChanged(boolean isReverse, int level, String name) { } }
3、BatteryMeterView
BatteryMeterView实现了 BatteryStateChangeCallback,收到改变监听 onBatteryLevelChanged()
//实现了 BatteryStateChangeCallback public class BatteryMeterView extends LinearLayout implements BatteryStateChangeCallback, Tunable, DarkReceiver, ConfigurationListener { ..... @Override public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { /*Bug 1296708 add charge animation of batteryView*/ if (mDrawable != null) { //是否绘制充电中闪电形状图标 mDrawable.setCharging(pluggedIn); //根据当前 level/100f 计算百分比绘制path mDrawable.setBatteryLevel(level); } mCharging = pluggedIn; mLevel = level; updatePercentText(); } .... }
八、NavigationBar导航栏模块
Ⅰ创建导航栏文件
1、StatusBar
①makeStatusBarView
// ================================================================================ // Constructing the view // ================================================================================ protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) { .... //创建导航栏 createNavigationBar(result); .....
②createNavigationBar
private final NavigationBarController mNavigationBarController; // TODO(b/117478341): This was left such that CarStatusBar can override this method. // Try to remove this. protected void createNavigationBar(@Nullable RegisterStatusBarResult result) { // mNavigationBarController.createNavigationBars(true /* includeDefaultDisplay */, result); ... }
2、NavigationBarController
①createNavigationBars
public void createNavigationBars(final boolean includeDefaultDisplay, RegisterStatusBarResult result) { Display[] displays = mDisplayManager.getDisplays(); for (Display display : displays) { if (includeDefaultDisplay || display.getDisplayId() != DEFAULT_DISPLAY) { // createNavigationBar(display, result); } } }
②createNavigationBar
/** * Adds a navigation bar on default display or an external display if the display supports * system decorations. * * @param display the display to add navigation bar on. */ @VisibleForTesting void createNavigationBar(Display display, RegisterStatusBarResult result) { if (display == null) { return; } final int displayId = display.getDisplayId(); final boolean isOnDefaultDisplay = displayId == DEFAULT_DISPLAY; final IWindowManager wms = WindowManagerGlobal.getWindowManagerService(); try { if (!wms.hasNavigationBar(displayId)) { return; } } catch (RemoteException e) { // Cannot get wms, just return with warning message. Log.w(TAG, "Cannot get WindowManager."); return; } final Context context = isOnDefaultDisplay ? mContext : mContext.createDisplayContext(display); //最终是通过NavigationBarFragment的create方法进行创建 NavigationBarFragment.create(context, (tag, fragment) -> { NavigationBarFragment navBar = (NavigationBarFragment) fragment;
3、NavigationBarFragment
①create
代码做了两件事:
1.创建navigationBarView 并且把navigationBarView添加到windowManager中。
2.创建NavigationBarFragment 替换navigation_bar_window的布局文件,改成navigation_bar
public static View create(Context context, FragmentListener listener) { WindowManager.LayoutParams lp = new WindowManager.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_NAVIGATION_BAR, WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH | WindowManager.LayoutParams.FLAG_SLIPPERY, PixelFormat.TRANSLUCENT); lp.token = new Binder(); lp.setTitle("NavigationBar" + context.getDisplayId()); lp.accessibilityTitle = context.getString(R.string.nav_bar); lp.windowAnimations = 0; lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC; View navigationBarView = LayoutInflater.from(context).inflate( R.layout.navigation_bar_window, null); if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView); if (navigationBarView == null) return null; //创建NavigationBarFragment 替换navigation_bar_window的布局文件,改成navigation_bar final NavigationBarFragment fragment = FragmentHostManager.get(navigationBarView) .create(NavigationBarFragment.class); navigationBarView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { final FragmentHostManager fragmentHost = FragmentHostManager.get(v); //navigation_bar_frame是navigation_bar_window中NavigationBarFrame的ID fragmentHost.getFragmentManager().beginTransaction() .replace(R.id.navigation_bar_frame, fragment, TAG) .commit(); fragmentHost.addTagListener(TAG, listener); } @Override public void onViewDetachedFromWindow(View v) { FragmentHostManager.removeAndDestroy(v); navigationBarView.removeOnAttachStateChangeListener(this); } }); context.getSystemService(WindowManager.class).addView(navigationBarView, lp); return navigationBarView; }
③onCreateView
@Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { // return inflater.inflate(R.layout.navigation_bar, container, false); }
4、navigation_bar_window.xml
<com.android.systemui.statusbar.phone.NavigationBarFrame xmlns:android="http://schemas.android.com/apk/res/android" xmlns:systemui="http://schemas.android.com/apk/res-auto" android:id="@+id/navigation_bar_frame" android:theme="@style/Theme.SystemUI" android:layout_height="match_parent" android:layout_width="match_parent"> </com.android.systemui.statusbar.phone.NavigationBarFrame>
5、navigation_bar.xml
<com.android.systemui.statusbar.phone.NavigationBarView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:systemui="http://schemas.android.com/apk/res-auto" android:layout_height="match_parent" android:layout_width="match_parent" android:background="@drawable/system_bar_background"> <com.android.systemui.CornerHandleView android:id="@+id/assist_hint_left" android:layout_width="40dp" android:layout_height="40dp" android:layout_gravity="left|bottom" android:rotation="270" android:visibility="gone"/> <com.android.systemui.CornerHandleView android:id="@+id/assist_hint_right" android:layout_width="40dp" android:layout_height="40dp" android:layout_gravity="right|bottom" android:rotation="180" android:visibility="gone"/> <com.android.systemui.statusbar.phone.NavigationBarInflaterView android:id="@+id/navigation_inflater" android:layout_width="match_parent" android:layout_height="match_parent" /> </com.android.systemui.statusbar.phone.NavigationBarView>
Ⅱ加载布局文件
6、NavigationBarInflaterView
①NavigationBarInflaterView(Context context, AttributeSet attrs)
public NavigationBarInflaterView(Context context, AttributeSet attrs) { super(context, attrs); createInflaters(); mOverviewProxyService = Dependency.get(OverviewProxyService.class); mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this); /* UNISOC: add for bug 1071183,1134237 @{ */ mSupportDynamicBar = NavigationBarView.isSupportDynamicNavBar(context, mNavBarMode); /* }@ */ }
②createInflaters
@VisibleForTesting void createInflaters() { mLayoutInflater = LayoutInflater.from(mContext); Configuration landscape = new Configuration(); landscape.setTo(mContext.getResources().getConfiguration()); landscape.orientation = Configuration.ORIENTATION_LANDSCAPE; mLandscapeInflater = LayoutInflater.from(mContext.createConfigurationContext(landscape)); }
③onFinishInflate
@Override protected void onFinishInflate() { super.onFinishInflate(); //添加水平横屏布局 inflateChildren(); //清空布局 clearViews(); inflateLayout(getDefaultLayout()); }
④inflateChildren
private void inflateChildren() { removeAllViews(); //水平布局 mHorizontal = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout, this /* root */, false /* attachToRoot */); addView(mHorizontal); //垂直布局 mVertical = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout_vertical, this /* root */, false /* attachToRoot */); addView(mVertical); updatealternativeorder(); }
⑤clearViews
private void clearViews() { if (mButtonDispatchers != null) { for (int i = 0; i < mButtonDispatchers.size(); i++) { mButtonDispatchers.valueAt(i).clear(); } } clearAllChildren(mHorizontal.findViewById(R.id.nav_buttons)); clearAllChildren(mVertical.findViewById(R.id.nav_buttons)); } private void clearAllChildren(ViewGroup group) { for (int i = 0; i < group.getChildCount(); i++) { ((ViewGroup) group.getChildAt(i)).removeAllViews(); } }
⑥inflateLayout
protected void inflateLayout(String newLayout) { mCurrentLayout = newLayout; if (newLayout == null) { newLayout = getDefaultLayout(); } String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3); if (sets.length != 3) { Log.d(TAG, "Invalid layout."); newLayout = getDefaultLayout(); sets = newLayout.split(GRAVITY_SEPARATOR, 3); } String[] start = sets[0].split(BUTTON_SEPARATOR); String[] center = sets[1].split(BUTTON_SEPARATOR); String[] end = sets[2].split(BUTTON_SEPARATOR); // Inflate these in start to end order or accessibility traversal will be messed up. inflateButtons(start, mHorizontal.findViewById(R.id.ends_group), false /* landscape */, true /* start */); inflateButtons(start, mVertical.findViewById(R.id.ends_group), true /* landscape */, true /* start */); inflateButtons(center, mHorizontal.findViewById(R.id.center_group), false /* landscape */, false /* start */); inflateButtons(center, mVertical.findViewById(R.id.center_group), true /* landscape */, false /* start */); addGravitySpacer(mHorizontal.findViewById(R.id.ends_group)); addGravitySpacer(mVertical.findViewById(R.id.ends_group)); inflateButtons(end, mHorizontal.findViewById(R.id.ends_group), false /* landscape */, false /* start */); inflateButtons(end, mVertical.findViewById(R.id.ends_group), true /* landscape */, false /* start */); updateButtonDispatchersCurrentView(); }
⑦inflateButton
private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape, boolean start) { for (int i = 0; i < buttons.length; i++) { inflateButton(buttons[i], parent, landscape, start); } } @Nullable protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape, boolean start) { LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater; View v = createView(buttonSpec, parent, inflater); if (v == null) return null; v = applySize(v, buttonSpec, landscape, start); parent.addView(v); addToDispatchers(v); View lastView = landscape ? mLastLandscape : mLastPortrait; View accessibilityView = v; if (v instanceof ReverseRelativeLayout) { accessibilityView = ((ReverseRelativeLayout) v).getChildAt(0); } if (lastView != null) { accessibilityView.setAccessibilityTraversalAfter(lastView.getId()); } if (landscape) { mLastLandscape = accessibilityView; } else { mLastPortrait = accessibilityView; } return v; }
⑧getDefaultLayout
//导航栏显示哪些控件是由getDefaultLayout来决定 protected String getDefaultLayout() { /* UNISOC: Bug 1072090 new feature of dynamic navigationbar @{ */ if (mSupportDynamicBar) { return readLNavigationLayoutSettings(); } /* @} */ final int defaultResource = QuickStepContract.isGesturalMode(mNavBarMode) ? R.string.config_navBarLayoutHandle : mOverviewProxyService.shouldShowSwipeUpUI() ? R.string.config_navBarLayoutQuickstep : R.string.config_navBarLayout; return getContext().getString(defaultResource); }
⑨createView
private View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) { View v = null; String button = extractButton(buttonSpec); if (LEFT.equals(button)) { button = extractButton(NAVSPACE); } else if (RIGHT.equals(button)) { button = extractButton(MENU_IME_ROTATE); } if (HOME.equals(button)) { v = inflater.inflate(R.layout.home, parent, false); } else if (BACK.equals(button)) { v = inflater.inflate(R.layout.back, parent, false); } else if (RECENT.equals(button)) { v = inflater.inflate(R.layout.recent_apps, parent, false); } else if (MENU_IME_ROTATE.equals(button)) { v = inflater.inflate(R.layout.menu_ime, parent, false); } else if (NAVSPACE.equals(button)) { v = inflater.inflate(R.layout.nav_key_space, parent, false); } else if (CLIPBOARD.equals(button)) { v = inflater.inflate(R.layout.clipboard, parent, false); } else if (CONTEXTUAL.equals(button)) { v = inflater.inflate(R.layout.contextual, parent, false); } else if (HOME_HANDLE.equals(button)) { v = inflater.inflate(R.layout.home_handle, parent, false); } else if (IME_SWITCHER.equals(button)) { v = inflater.inflate(R.layout.ime_switcher, parent, false); } else if (button.startsWith(KEY)) { String uri = extractImage(button); int code = extractKeycode(button); v = inflater.inflate(R.layout.custom_key, parent, false); ((KeyButtonView) v).setCode(code); if (uri != null) { if (uri.contains(":")) { ((KeyButtonView) v).loadAsync(Icon.createWithContentUri(uri)); } else if (uri.contains("/")) { int index = uri.indexOf('/'); String pkg = uri.substring(0, index); int id = Integer.parseInt(uri.substring(index + 1)); ((KeyButtonView) v).loadAsync(Icon.createWithResource(pkg, id)); } } } /* UNISOC: Bug 1072090 new feature of dynamic navigationbar @{ */ else if (HIDE.equals(button)) { v = inflater.inflate(R.layout.hide, parent, false); } else if (PULL.equals(button)) { v = inflater.inflate(R.layout.pull, parent, false); /*UNISOC: Add for bug 902309 1146896 @{ */ } else if (SPACE_PLACE.equals(button)) { v = inflater.inflate(R.layout.space, parent, false); /* }@ */ } else { return null; } /* @} */ return v; }
7、home.xml、back.xml、recent_apps.xml
<com.android.systemui.statusbar.policy.KeyButtonView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:systemui="http://schemas.android.com/apk/res-auto" android:id="@+id/home" android:layout_width="@dimen/navigation_key_width" android:layout_height="match_parent" android:layout_weight="0" systemui:keyCode="3" android:scaleType="center" android:contentDescription="@string/accessibility_home" android:paddingStart="@dimen/navigation_key_padding" android:paddingEnd="@dimen/navigation_key_padding" /> <com.android.systemui.statusbar.policy.KeyButtonView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:systemui="http://schemas.android.com/apk/res-auto" android:id="@+id/back" android:layout_width="@dimen/navigation_key_width" android:layout_height="match_parent" android:layout_weight="0" systemui:keyCode="4" android:scaleType="center" android:contentDescription="@string/accessibility_back" android:paddingStart="@dimen/navigation_key_padding" android:paddingEnd="@dimen/navigation_key_padding" /> <com.android.systemui.statusbar.policy.KeyButtonView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:systemui="http://schemas.android.com/apk/res-auto" android:id="@+id/recent_apps" android:layout_width="@dimen/navigation_key_width" android:layout_height="match_parent" android:layout_weight="0" android:scaleType="center" android:contentDescription="@string/accessibility_recent" android:paddingStart="@dimen/navigation_key_padding" android:paddingEnd="@dimen/navigation_key_padding" />
8、config
<!-- Nav bar button default ordering/layout --> <string name="config_navBarLayout" translatable="false">left[.5W],back[1WC];home;recent[1WC],right[.5W]</string> <string name="config_navBarLayoutQuickstep" translatable="false">back[1.7WC];home;contextual[1.7WC]</string> <string name="config_navBarLayoutHandle" translatable="false">back[40AC];home_handle;ime_switcher[40AC]</string>
第一、导航栏显示哪些控件是由getDefaultLayout来决定。
<string name="config_navBarLayout" translatable="false">left[.5W],back[1WC];home;recent[1WC],right[.5W]</string>
一般情况下我们是home,recent,back这三个键,如果你需要加其他的就在这个配置文件夹。同时在createView添加对应的布局文件。
第二、createView方法创建对应的布局文件,并且添加到导航栏中。
那么我们现在布局文件都添加完成了,但是你会发现在NavigationBarInflaterView没有对资源文件添加的代码已经控件点击触摸事件处理逻辑。那么这两部分代码在哪里呢?
答案是:
1.NavigationBarView 完成资源文件添加。
2.NavigationBarFragment 添加点击事件和触摸事件的处理逻辑。
Ⅲ资源文件添加
1、NavigationBarView
①NavigationBarView(Context context, AttributeSet attrs)
public NavigationBarView(Context context, AttributeSet attrs) { super(context, attrs); mIsVertical = false; mLongClickableAccessibilityButton = false; mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this); //UNISOC: Add for bug 1242615 mOldNavBarMode = mNavBarMode; /* UNISCO: Bug 1072090,1116092 new feature of dynamic navigationbar @{*/ mSupportDynamicBar = isSupportDynamicNavBar(mContext, mNavBarMode); mKeyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); /* }@ */ /* UNISOC: Modify for bug963304 {@ */ mStatusBarManager = (StatusBarManager) mContext.getSystemService(android.app.Service.STATUS_BAR_SERVICE); /* @} */ boolean isGesturalMode = isGesturalMode(mNavBarMode); mSysUiFlagContainer = Dependency.get(SysUiState.class); mPluginManager = Dependency.get(PluginManager.class); // Set up the context group of buttons mContextualButtonGroup = new ContextualButtonGroup(R.id.menu_container); final ContextualButton imeSwitcherButton = new ContextualButton(R.id.ime_switcher, R.drawable.ic_ime_switcher_default); final RotationContextButton rotateSuggestionButton = new RotationContextButton( R.id.rotate_suggestion, R.drawable.ic_sysbar_rotate_button); final ContextualButton accessibilityButton = new ContextualButton(R.id.accessibility_button, R.drawable.ic_sysbar_accessibility_button); mContextualButtonGroup.addButton(imeSwitcherButton); if (!isGesturalMode) { mContextualButtonGroup.addButton(rotateSuggestionButton); } mContextualButtonGroup.addButton(accessibilityButton); mOverviewProxyService = Dependency.get(OverviewProxyService.class); mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService); mFloatingRotationButton = new FloatingRotationButton(context); mRotationButtonController = new RotationButtonController(context, R.style.RotateButtonCCWStart90, isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton); mConfiguration = new Configuration(); mTmpLastConfiguration = new Configuration(); mConfiguration.updateFrom(context.getResources().getConfiguration()); mScreenPinningNotify = new ScreenPinningNotify(mContext); mBarTransitions = new NavigationBarTransitions(this, Dependency.get(CommandQueue.class)); mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back)); mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home)); mButtonDispatchers.put(R.id.home_handle, new ButtonDispatcher(R.id.home_handle)); mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps)); mButtonDispatchers.put(R.id.ime_switcher, imeSwitcherButton); mButtonDispatchers.put(R.id.accessibility_button, accessibilityButton); mButtonDispatchers.put(R.id.rotate_suggestion, rotateSuggestionButton); mButtonDispatchers.put(R.id.menu_container, mContextualButtonGroup); mDeadZone = new DeadZone(this); /* UNISOC: Bug 1072090 new feature of dynamic navigationbar @{ */ if(mSupportDynamicBar){ mStatusBar = Dependency.get(StatusBar.class); mButtonDispatchers.put(R.id.hide, new ButtonDispatcher(R.id.hide)); mButtonDispatchers.put(R.id.pull, new ButtonDispatcher(R.id.pull)); } mNavColorSampleMargin = getResources() .getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin); //updateStates更新状态 mEdgeBackGestureHandler = new EdgeBackGestureHandler(context, mOverviewProxyService, mSysUiFlagContainer, mPluginManager, this::updateStates); mRegionSamplingHelper = new RegionSamplingHelper(this, new RegionSamplingHelper.SamplingCallback() { @Override public void onRegionDarknessChanged(boolean isRegionDark) { getLightTransitionsController().setIconsDark(!isRegionDark , true /* animate */); } @Override public Rect getSampledRegion(View sampledView) { if (mOrientedHandleSamplingRegion != null) { return mOrientedHandleSamplingRegion; } updateSamplingRect(); return mSamplingBounds; } @Override public boolean isSamplingEnabled() { return isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode); } }); }
②updateStates
public void updateStates() { final boolean showSwipeUpUI = mOverviewProxyService.shouldShowSwipeUpUI(); if (mNavigationInflaterView != null) { // Reinflate the navbar if needed, no-op unless the swipe up state changes mNavigationInflaterView.onLikelyDefaultLayoutChange(); } updateSlippery(); //初始化加载资源,主要是图片 reloadNavIcons(); updateNavButtonIcons(); setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled()); WindowManagerWrapper.getInstance().setNavBarVirtualKeyHapticFeedbackEnabled(!showSwipeUpUI); getHomeButton().setAccessibilityDelegate( showSwipeUpUI ? mQuickStepAccessibilityDelegate : null); }
③reloadNavIcons
//初始化加载资源,主要是图片 private void reloadNavIcons() { updateIcons(Configuration.EMPTY); }
Ⅳ添加点击事件和触摸事件的处理逻辑。
1、NavigationBarFragment
①onViewCreated
@Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mNavigationBarView = (NavigationBarView) view; final Display display = view.getDisplay(); // It may not have display when running unit test. if (display != null) { mDisplayId = display.getDisplayId(); mIsOnDefaultDisplay = mDisplayId == Display.DEFAULT_DISPLAY; } mNavigationBarView.setComponents(mStatusBarLazy.get().getPanelController()); mNavigationBarView.setDisabledFlags(mDisabledFlags1); mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged); mNavigationBarView.setOnTouchListener(this::onNavigationTouch); if (savedInstanceState != null) { mNavigationBarView.getLightTransitionsController().restoreState(savedInstanceState); } mNavigationBarView.setNavigationIconHints(mNavigationIconHints); mNavigationBarView.setWindowVisible(isNavBarWindowVisible()); 添加home,recent触摸事件回调 prepareNavigationBarView(); checkNavBarModes(); IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_USER_SWITCHED); //UNISOC: Add for bug 1274603 filter.addAction(Intent.ACTION_USER_PRESENT); filter.addAction(PowerManagerEx.ACTION_POWEREX_SAVE_MODE_CHANGED); mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, Handler.getMain(), UserHandle.ALL); notifyNavigationBarScreenOn(); mOverviewProxyService.addCallback(mOverviewProxyListener); updateSystemUiStateFlags(-1); ...... }
②prepareNavigationBarView
setOnClickListener,setOnTouchListener,setLongClickable,setOnLongClickListener就是给对应的控件添加控制代码
private void prepareNavigationBarView() { mNavigationBarView.reorient(); ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton(); recentsButton.setOnClickListener(this::onRecentsClick); recentsButton.setOnTouchListener(this::onRecentsTouch); recentsButton.setLongClickable(true); recentsButton.setOnLongClickListener(this::onLongPressBackRecents); ButtonDispatcher backButton = mNavigationBarView.getBackButton(); backButton.setLongClickable(true); ButtonDispatcher homeButton = mNavigationBarView.getHomeButton(); homeButton.setOnTouchListener(this::onHomeTouch); homeButton.setOnLongClickListener(this::onHomeLongClick); ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton(); accessibilityButton.setOnClickListener(this::onAccessibilityClick); accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick); updateAccessibilityServicesState(mAccessibilityManager); updateScreenPinningGestures(); }
③onHomeTouch
private boolean onHomeTouch(View v, MotionEvent event) { if (mHomeBlockedThisTouch && event.getActionMasked() != MotionEvent.ACTION_DOWN) { return true; } // If an incoming call is ringing, HOME is totally disabled. // (The user is already on the InCallUI at this point, // and his ONLY options are to answer or reject the call.) switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mHomeBlockedThisTouch = false; TelecomManager telecomManager = getContext().getSystemService(TelecomManager.class); if (telecomManager != null && telecomManager.isRinging()) { if (mStatusBarLazy.get().isKeyguardShowing()) { Log.i(TAG, "Ignoring HOME; there's a ringing incoming call. " + "No heads up"); mHomeBlockedThisTouch = true; return true; } } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mStatusBarLazy.get().awakenDreams(); break; } return false; }
④onHomeLongClick
@VisibleForTesting boolean onHomeLongClick(View v) { if (!mNavigationBarView.isRecentsButtonVisible() && ActivityManagerWrapper.getInstance().isScreenPinningActive()) { return onLongPressBackHome(v); } if (shouldDisableNavbarGestures()) { return false; } mMetricsLogger.action(MetricsEvent.ACTION_ASSIST_LONG_PRESS); /* UNISOC: Bug 1074234, 970184, Super power feature @{ */ if (UnisocPowerManagerUtil.isSuperPower()) { Log.d(TAG, "onHomeLongClick SUPPORT_SUPER_POWER_SAVE ignore!"); return false; } /* @} */ mUiEventLogger.log(NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS); Bundle args = new Bundle(); args.putInt( AssistManager.INVOCATION_TYPE_KEY, AssistManager.INVOCATION_HOME_BUTTON_LONG_PRESS); mAssistManager.startAssist(args); mStatusBarLazy.get().awakenDreams(); if (mNavigationBarView != null) { mNavigationBarView.abortCurrentGesture(); } return true; }
九、Recents模块
packages/apps/SystemUI/src/com/android/systemui/recents/
1、Recents
public class Recents extends SystemUI implements CommandQueue.Callbacks { private final RecentsImplementation mImpl; private final CommandQueue mCommandQueue; public Recents(Context context, RecentsImplementation impl, CommandQueue commandQueue) { super(context); mImpl = impl; mCommandQueue = commandQueue; } //由前面SystemUI的启动可知,调用的都为子类的start方法 //在start方法添加了回调和调用了RecentsImplementation的onStart方法,下面跟进RecentsImplementation @Override public void start() { mCommandQueue.addCallback(this); mImpl.onStart(mContext); } ... }
2、RecentsImplementation
public interface RecentsImplementation { //可以看到该接口中方法皆为default修饰的方法,但均未写函数体,具体实现由子类实现,于是跟进OverviewProxyRecentsImpl类 default void onStart(Context context) {} default void onBootCompleted() {} default void onAppTransitionFinished() {} default void onConfigurationChanged(Configuration newConfig) {} default void preloadRecentApps() {} default void cancelPreloadRecentApps() {} default void showRecentApps(boolean triggeredFromAltTab) {} default void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {} default void toggleRecentApps() {} default void growRecents() {} default boolean splitPrimaryTask(int stackCreateMode, Rect initialBounds, int metricsDockAction) { return false; } default void dump(PrintWriter pw) {} }
3、OverviewProxyRecentsImpl
/** * An implementation of the Recents interface which proxies to the OverviewProxyService. */ @Singleton public class OverviewProxyRecentsImpl implements RecentsImplementation { private final static String TAG = "OverviewProxyRecentsImpl"; @Nullable private final Lazy<StatusBar> mStatusBarLazy; private final Optional<Divider> mDividerOptional; private Context mContext; private Handler mHandler; private TrustManager mTrustManager; private OverviewProxyService mOverviewProxyService; @SuppressWarnings("OptionalUsedAsFieldOrParameterType") @Inject public OverviewProxyRecentsImpl(Optional<Lazy<StatusBar>> statusBarLazy, Optional<Divider> dividerOptional) { mStatusBarLazy = statusBarLazy.orElse(null); mDividerOptional = dividerOptional; } //可见之前调用的onStart()方法具体是调用的该子类的重写方法 @Override public void onStart(Context context) { mContext = context; mHandler = new Handler(); mTrustManager = (TrustManager) context.getSystemService(Context.TRUST_SERVICE); mOverviewProxyService = Dependency.get(OverviewProxyService.class); } @Override public void toggleRecentApps() { // If connected to launcher service, let it handle the toggle logic IOverviewProxy overviewProxy = mOverviewProxyService.getProxy(); if (overviewProxy != null) { final Runnable toggleRecents = () -> { try { if (mOverviewProxyService.getProxy() != null) { //可以看到显示最近的app的方法都是通过得到OverviewProxyService的代理,之后对其操作, //接着跟进OverviewProxyService类查看overviewProxy的由来 mOverviewProxyService.getProxy().onOverviewToggle(); mOverviewProxyService.notifyToggleRecentApps(); } } catch (RemoteException e) { Log.e(TAG, "Cannot send toggle recents through proxy service.", e); } }; // Preload only if device for current user is unlocked if (mStatusBarLazy != null && mStatusBarLazy.get().isKeyguardShowing()) { mStatusBarLazy.get().executeRunnableDismissingKeyguard(() -> { // Flush trustmanager before checking device locked per user mTrustManager.reportKeyguardShowingChanged(); mHandler.post(toggleRecents); }, null, true /* dismissShade */, false /* afterKeyguardGone */, true /* deferred */); } else { toggleRecents.run(); } return; } else { // Do nothing } } }
4、OverviewProxyService
①getProxy
private IOverviewProxy mOverviewProxy; //可以看到getProxy()方法返回的是一个mOverviewProxy:IOverviewProxy对象引用,接下来查看其具体指向哪个对象 public IOverviewProxy getProxy() { return mOverviewProxy; }
②ServiceConnection
//通过mOverviewServiceConnection应该可以发现,应该是bindService中的一个参数。 private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { if (SysUiState.DEBUG) { Log.d(TAG_OPS, "Overview proxy service connected"); } mConnectionBackoffAttempts = 0; mHandler.removeCallbacks(mDeferredConnectionCallback); try { service.linkToDeath(mOverviewServiceDeathRcpt, 0); } catch (RemoteException e) { // Failed to link to death (process may have died between binding and connecting), // just unbind the service for now and retry again Log.e(TAG_OPS, "Lost connection to launcher service", e); disconnectFromLauncherService(); retryConnectionWithBackoff(); return; } mCurrentBoundedUserId = getCurrentUserId(); //mOverviewProxy指向了IOverviewProxy的一个远程代理 mOverviewProxy = IOverviewProxy.Stub.asInterface(service); Bundle params = new Bundle(); params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder()); params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius); params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows); try { mOverviewProxy.onInitialize(params); } catch (RemoteException e) { mCurrentBoundedUserId = -1; Log.e(TAG_OPS, "Failed to call onInitialize()", e); } dispatchNavButtonBounds(); ...... }
③internalConnectToCurrentUser
private void internalConnectToCurrentUser() { disconnectFromLauncherService(); // If user has not setup yet or already connected, do not try to connect if (!isEnabled()) { Log.v(TAG_OPS, "Cannot attempt connection, is enabled " + isEnabled()); return; } mHandler.removeCallbacks(mConnectionRunnable); //ACTION_QUICKSTEP,这个action就是Launcher中的 Intent launcherServiceIntent = new Intent(ACTION_QUICKSTEP); if (mRecentsComponentName != null) { launcherServiceIntent.setPackage(mRecentsComponentName.getPackageName()); } try { //传入的intent为launcherServiceIntent,其参数为ACTION_QUICKSTEP,查看定义这个action就是Launcher中的 mBound = mContext.bindServiceAsUser(launcherServiceIntent, mOverviewServiceConnection, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, UserHandle.of(getCurrentUserId())); } catch (SecurityException e) { Log.e(TAG_OPS, "Unable to bind because of security error", e); } catch (IllegalArgumentException e) { Log.e(TAG_OPS, "Unable to bind because of illegal argument error", e); } if (mBound) { // Ensure that connection has been established even if it thinks it is bound mHandler.postDelayed(mDeferredConnectionCallback, DEFERRED_CALLBACK_MILLIS); } else { // Retry after exponential backoff timeout retryConnectionWithBackoff(); } }
5、AndroidManifest.xml
代码位于packages/apps/Launcher3/quickstep/AndroidManifest.xml
<service //TouchInteractionService android:name="com.android.quickstep.TouchInteractionService" android:permission="android.permission.STATUS_BAR_SERVICE" android:directBootAware="true" > <intent-filter> <action android:name="android.intent.action.QUICKSTEP_SERVICE" /> </intent-filter> </service>
6、TouchInteractionService
private OverviewCommandHelper mOverviewCommandHelper; private final IBinder mMyBinder = new IOverviewProxy.Stub() { @BinderThread public void onInitialize(Bundle bundle) { ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface( bundle.getBinder(KEY_EXTRA_SYSUI_PROXY)); MAIN_EXECUTOR.execute(() -> { SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy); TouchInteractionService.this.initInputMonitor(); preloadOverview(true /* fromInit */); }); sIsInitialized = true; } @BinderThread @Override //也就是说,上面OverviewProxyRecentsImpl调用的mOverviewProxyService.getProxy().onOverviewToggle() //其实是调用TouchInteractionService中的mMyBinder的实现,mMyBinder就是IOverviewProxy的一个远程代理。 public void onOverviewToggle() { TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle"); mOverviewCommandHelper.onOverviewToggle(); } .... }
7、OverviewCommandHelper
①onOverviewToggle
//也就是说,上面OverviewProxyRecentsImpl调用的mOverviewProxyService.getProxy().onOverviewToggle() @BinderThread public void onOverviewToggle() { // If currently screen pinning, do not enter overview if (mDeviceState.isScreenPinningActive()) { return; } ActivityManagerWrapper.getInstance() .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); //可以看到这里主要启动了RecentsActivityCommand线程 MAIN_EXECUTOR.execute(new RecentsActivityCommand<>()); } public RecentsActivityCommand() { mActivityInterface = mOverviewComponentObserver.getActivityInterface(); mCreateTime = SystemClock.elapsedRealtime(); mAnimationProvider = new AppToOverviewAnimationProvider<>(mActivityInterface, RecentsModel.getRunningTaskId(), mDeviceState); // Preload the plan mRecentsModel.getTasks(null); }
②run()
最终实现了一个从点击switch到Launcher的RecentsActivity启动的过程
@Override public void run() { long elapsedTime = mCreateTime - mLastToggleTime; mLastToggleTime = mCreateTime; if (handleCommand(elapsedTime)) { // Command already handled. return; } if (mActivityInterface.switchToRecentsIfVisible(this::onTransitionComplete)) { // If successfully switched, then return return; } // Otherwise, start overview. mListener = mActivityInterface.createActivityInitListener(this::onActivityReady); mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(), new RemoteAnimationProvider() { @Override public AnimatorSet createWindowAnimation( RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets) { return RecentsActivityCommand.this.createWindowAnimation(appTargets, wallpaperTargets); } }, mContext, MAIN_EXECUTOR.getHandler(), mAnimationProvider.getRecentsLaunchDuration()); }
十、VolumeUI模块
这个模块使用MVP架构完成设计的
通过 SystemUI之StatusBar创建 可知,VolumeUI 的入口为 VolumeUI#start()
Ⅰ MVP架构绑定流程
1、VolumeUI
①start()
@Override public void start() { boolean enableVolumeUi = mContext.getResources().getBoolean(R.bool.enable_volume_ui); boolean enableSafetyWarning = mContext.getResources().getBoolean(R.bool.enable_safety_warning); mEnabled = enableVolumeUi || enableSafetyWarning; if (!mEnabled) return; //mVolumeComponent从名字可以看出,它代表 VolumeUI 组件,通过它可以创建整个MVP。 mVolumeComponent.setEnableDialogs(enableVolumeUi, enableSafetyWarning); //register启动VolumeUI的功能 setDefaultVolumeController(); }
②VolumeUI(Context context, VolumeDialogComponent volumeDialogComponent)
//VolumeUI 启动的时候会创建一个 VolumeDialogComponent 对象 @Inject public VolumeUI(Context context, VolumeDialogComponent volumeDialogComponent) { super(context); mVolumeComponent = volumeDialogComponent; }
③setDefaultVolumeController
private void setDefaultVolumeController() { DndTile.setVisible(mContext, true); if (D.BUG) Log.d(TAG, "Registering default volume controller"); //VolumeDialogComponent 对象创建完成后,就会调用它的register()方法启动 VolumeUI 功能。 //register启动VolumeUI的功能 //它其实就是关联 Presenter 层和 Model 层。 mVolumeComponent.register(); }
④VolumeDialogComponent的register方法
private final VolumeDialogControllerImpl mController; @Override public void register() { mController.register(); DndTile.setCombinedIcon(mContext, true); }
⑤VolumeDialogControllerImpl的register方法
public void register() { setVolumeController(); setVolumePolicy(mVolumePolicy); showDndTile(mShowDndTile); try { mMediaSessions.init(); } catch (SecurityException e) { Log.w(TAG, "No access to media sessions", e); } }
2、VolumeDialogComponent
①VolumeDialogComponent 的构造函数
//接口,实现类为VolumeDialogImpl private VolumeDialog mDialog; @Inject public VolumeDialogComponent(Context context, KeyguardViewMediator keyguardViewMediator, VolumeDialogControllerImpl volumeDialogController) { mContext = context; mKeyguardViewMediator = keyguardViewMediator; mController = volumeDialogController; mController.setUserActivityListener(this); // Allow plugins to reference the VolumeDialogController. Dependency.get(PluginDependencyProvider.class) .allowPluginDependency(VolumeDialogController.class); Dependency.get(ExtensionController.class).newExtension(VolumeDialog.class) .withPlugin(VolumeDialog.class) //VolumeDialogComponent 通过 createDefault() 创建 VolumeDialogImpl 对象,它代表 View 层 .withDefault(this::createDefault) .withCallback(dialog -> { if (mDialog != null) { mDialog.destroy(); } mDialog = dialog; //然后通过init() 进行了初始化。 mDialog.init(LayoutParams.TYPE_VOLUME_OVERLAY, mVolumeDialogCallback); }).build(); applyConfiguration(); Dependency.get(TunerService.class).addTunable(this, VOLUME_DOWN_SILENT, VOLUME_UP_SILENT, VOLUME_SILENT_DO_NOT_DISTURB); }
②createDefault()
protected VolumeDialog createDefault() { VolumeDialogImpl impl = new VolumeDialogImpl(mContext); impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false); impl.setAutomute(true); impl.setSilentMode(false); return impl; }
2、VolumeDialogImpl---View层
①init(int windowType, Callback callback)
public void init(int windowType, Callback callback) { initDialog(); mAccessibility.init(); //向 VolumeDialogControllerImpl (Presenter层) 注册一个回调 //也就是 View 层与 Presenter 层建立关联,从而可以通过 Presenter 层控制 View 层。 mController.addCallback(mControllerCallbackH, mHandler); mController.getState(); Dependency.get(ConfigurationController.class).addCallback(this); }
3、VolumeDialogControllerImpl---P层
①register
//进行AudioManager的关联,也就是presenter层和model层的关联 public void register() { //进行AudioManager的关联 setVolumeController(); setVolumePolicy(mVolumePolicy); showDndTile(mShowDndTile); try { mMediaSessions.init(); } catch (SecurityException e) { Log.w(TAG, "No access to media sessions", e); } }
②setVolumeController()
private AudioManager mAudio; protected final VC mVolumeController = new VC(); protected void setVolumeController() { try { mAudio.setVolumeController(mVolumeController); } catch (SecurityException e) { Log.w(TAG, "Unable to set the volume controller", e); return; } }
Ⅱ 按下 Power 键后,VolumeUI 是如何显示UI的
由于 VolumeDialogControllerImpl 向AudioManager注册了回调,当按下音量键调整了音量后,VolumeDialogControllerImpl 就会收到回调
1、VC(VolumeDialogControllerImpl内部类)
private final W mWorker; private final class VC extends IVolumeController.Stub { private final String TAG = VolumeDialogControllerImpl.TAG + ".VC"; ...... @Override public void volumeChanged(int streamType, int flags) throws RemoteException { if (D.BUG) Log.d(TAG, "volumeChanged " + AudioSystem.streamToString(streamType) + " " + Util.audioManagerFlagsToString(flags)); if (mDestroyed) return; //mWorker为继承于Handler的内部final类,根据收到的消息不同处理 mWorker.obtainMessage(W.VOLUME_CHANGED, streamType, flags).sendToTarget(); } .......... }
2、W(VolumeDialogControllerImpl内部类)
①handleMessage
private final class W extends Handler { private static final int VOLUME_CHANGED = 1; private static final int DISMISS_REQUESTED = 2; ..... W(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { switch (msg.what) { //当消息为VOLUME_CHANGED时,调用onVolumeChangedW方法 case VOLUME_CHANGED: onVolumeChangedW(msg.arg1, msg.arg2); break; case DISMISS_REQUESTED: onDismissRequestedW(msg.arg1); break; case GET_STATE: onGetStateW(); break; case SET_RINGER_MODE: onSetRingerModeW(msg.arg1, msg.arg2 != 0); break; case SET_ZEN_MODE: onSetZenModeW(msg.arg1); break; case SET_EXIT_CONDITION: onSetExitConditionW((Condition) msg.obj); break; case SET_STREAM_MUTE: onSetStreamMuteW(msg.arg1, msg.arg2 != 0); break; case LAYOUT_DIRECTION_CHANGED: mCallbacks.onLayoutDirectionChanged(msg.arg1); break; case CONFIGURATION_CHANGED: mCallbacks.onConfigurationChanged(); break; case SET_STREAM_VOLUME: onSetStreamVolumeW(msg.arg1, msg.arg2); break; case SET_ACTIVE_STREAM: onSetActiveStreamW(msg.arg1); break; case NOTIFY_VISIBLE: onNotifyVisibleW(msg.arg1 != 0); break; case USER_ACTIVITY: onUserActivityW(); break; case SHOW_SAFETY_WARNING: onShowSafetyWarningW(msg.arg1); break; case GET_CAPTIONS_COMPONENT_STATE: onGetCaptionsComponentStateW((Boolean) msg.obj); break; case ACCESSIBILITY_MODE_CHANGED: onAccessibilityModeChanged((Boolean) msg.obj); } } }
②onVolumeChangedW
boolean onVolumeChangedW(int stream, int flags) { //根据 flags 决定要执行哪个回调,如果要显示UI,就会回调 onShowRequested() final boolean showUI = shouldShowUI(flags); final boolean fromKey = (flags & AudioManager.FLAG_FROM_KEY) != 0; final boolean showVibrateHint = (flags & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0; final boolean showSilentHint = (flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0; boolean changed = false; if (showUI) { changed |= updateActiveStreamW(stream); } int lastAudibleStreamVolume = getAudioManagerStreamVolume(stream); changed |= updateStreamLevelW(stream, lastAudibleStreamVolume); changed |= checkRoutedToBluetoothW(showUI ? AudioManager.STREAM_MUSIC : stream); if (changed) { mCallbacks.onStateChanged(mState); } //这个回调当然是由 View 层实现的,也就是在VolumeDialogImpl中调用 if (showUI) { mCallbacks.onShowRequested(Events.SHOW_REASON_VOLUME_CHANGED); } if (showVibrateHint) { mCallbacks.onShowVibrateHint(); } if (showSilentHint) { mCallbacks.onShowSilentHint(); } if (changed && fromKey) { Events.writeEvent(Events.EVENT_KEY, stream, lastAudibleStreamVolume); } return changed; }
3、VolumeDialogImpl
①onShowRequested
public void init(int windowType, Callback callback) { initDialog(); mAccessibility.init(); //初始化的时候添加回调addCallback mController.addCallback(mControllerCallbackH, mHandler); mController.getState(); Dependency.get(ConfigurationController.class).addCallback(this); } //mControllerCallbackH private final VolumeDialogController.Callbacks mControllerCallbackH = new VolumeDialogController.Callbacks() { @Override public void onShowRequested(int reason) { // showH(reason); } }
②showH
private void showH(int reason) { if (D.BUG) Log.d(TAG, "showH r=" + Events.SHOW_REASONS[reason]); mHandler.removeMessages(H.SHOW); mHandler.removeMessages(H.DISMISS); rescheduleTimeoutH(); /* UNISOC: Modify for bug1347675,1384445 @{ */ Configuration config = mContext.getResources().getConfiguration(); boolean orientationPortrait = config.orientation == ORIENTATION_PORTRAIT; if ((mConfigChanged || (mOrientationPortrait != orientationPortrait)) && !mDialog.isShowing()) { initDialog(); // resets mShowing to false mConfigurableTexts.update(); mConfigChanged = false; mOrientationPortrait = orientationPortrait; } /* @} */ initSettingsH(); mShowing = true; mIsAnimatingDismiss = false; mDialog.show(); Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked()); mController.notifyVisible(true); mController.getCaptionsComponentState(false); checkODICaptionsTooltip(false); }
ⅢAudioService对音量键处理流程
1、PhoneWindowManager
①dispatchDirectAudioEvent
// pre-condition: event.getKeyCode() is one of KeyEvent.KEYCODE_VOLUME_UP, // KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_MUTE private void dispatchDirectAudioEvent(KeyEvent event) { // When System Audio Mode is off, volume keys received by AVR can be either consumed by AVR // or forwarded to the TV. It's up to Amplifier manufacturer’s implementation. HdmiControlManager hdmiControlManager = getHdmiControlManager(); if (null != hdmiControlManager && !hdmiControlManager.getSystemAudioMode() && shouldCecAudioDeviceForwardVolumeKeysSystemAudioModeOff()) { HdmiAudioSystemClient audioSystemClient = hdmiControlManager.getAudioSystemClient(); if (audioSystemClient != null) { audioSystemClient.sendKeyEvent( event.getKeyCode(), event.getAction() == KeyEvent.ACTION_DOWN); return; } } try { //这里通过AIDL获取IAudioService的实例 getAudioService().handleVolumeKey(event, mUseTvRouting, mContext.getOpPackageName(), TAG); } catch (Exception e) { Log.e(TAG, "Error dispatching volume key in handleVolumeKey for event:" + event, e); } }
②getAudioService()
import android.media.IAudioService; static IAudioService getAudioService() { IAudioService audioService = IAudioService.Stub.asInterface( ServiceManager.checkService(Context.AUDIO_SERVICE)); if (audioService == null) { Log.w(TAG, "Unable to find IAudioService interface."); } return audioService; }
这里是直接执行了音频键的操作,通过Binder获取到了AudioService的实例,去调用了handleVolumeKey方法,参数含义如下:
按键类型 | Audio Service操作类型 | 含义 |
KEYCODE_VOLUME_UP | AudioManager.ADJUST_RAISE | 音量加 |
KEYCODE_VOLUME_DOWN | AudioManager.ADJUST_LOWER | 音量减 |
KEYCODE_VOLUME_MUTE | AudioManager.ADJUST_TOGGLE_MUTE | 改变静音状态 |
2、AudioService
①handleVolumeKey
//AudioService继承了IAudioService public class AudioService extends IAudioService.Stub implements AccessibilityManager.TouchExplorationStateChangeListener, AccessibilityManager.AccessibilityServicesStateChangeListener { // pre-condition: event.getKeyCode() is one of KeyEvent.KEYCODE_VOLUME_UP, // KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_MUTE public void handleVolumeKey(@NonNull KeyEvent event, boolean isOnTv, @NonNull String callingPackage, @NonNull String caller) { int keyEventMode = VOL_ADJUST_NORMAL; if (isOnTv) { if (event.getAction() == KeyEvent.ACTION_DOWN) { keyEventMode = VOL_ADJUST_START; } else { // may catch more than ACTION_UP, but will end vol adjustement // the vol key is either released (ACTION_UP), or multiple keys are pressed // (ACTION_MULTIPLE) and we don't know what to do for volume control on CEC, end // the repeated volume adjustement keyEventMode = VOL_ADJUST_END; } } else if (event.getAction() != KeyEvent.ACTION_DOWN) { return; } int flags = AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_FROM_KEY; switch (event.getKeyCode()) { case KeyEvent.KEYCODE_VOLUME_UP: //在按键的处理过程中,并没有将相应的code传递给AudioService, //而是使用了相关的定义,将KEYCODE_VOLUME_UP等操作转化为了ADJUST_RAISE等。 //而flag存储了一些对音量的要求或者信息吧,这个也很重要。 adjustSuggestedStreamVolume(AudioManager.ADJUST_RAISE, AudioManager.USE_DEFAULT_STREAM_TYPE, flags, callingPackage, caller, Binder.getCallingUid(), true, keyEventMode); break; case KeyEvent.KEYCODE_VOLUME_DOWN: adjustSuggestedStreamVolume(AudioManager.ADJUST_LOWER, AudioManager.USE_DEFAULT_STREAM_TYPE, flags, callingPackage, caller, Binder.getCallingUid(), true, keyEventMode); break; case KeyEvent.KEYCODE_VOLUME_MUTE: if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { adjustSuggestedStreamVolume(AudioManager.ADJUST_TOGGLE_MUTE, AudioManager.USE_DEFAULT_STREAM_TYPE, flags, callingPackage, caller, Binder.getCallingUid(), true, VOL_ADJUST_NORMAL); } break; default: Log.e(TAG, "Invalid key code " + event.getKeyCode() + " sent by " + callingPackage); return; // not needed but added if code gets added below this switch statement } } }
②adjustSuggestedStreamVolume
private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags, String callingPackage, String caller, int uid, boolean hasModifyAudioSettings, int keyEventMode) { if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream=" + suggestedStreamType + ", flags=" + flags + ", caller=" + caller + ", volControlStream=" + mVolumeControlStream + ", userSelect=" + mUserSelectedVolumeControlStream); if (direction != AudioManager.ADJUST_SAME) { sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_SUGG_VOL, suggestedStreamType, direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage) .append("/").append(caller).append(" uid:").append(uid).toString())); } boolean hasExternalVolumeController = notifyExternalVolumeController(direction); new MediaMetrics.Item(mMetricsId + "adjustSuggestedStreamVolume") .setUid(Binder.getCallingUid()) .set(MediaMetrics.Property.CALLING_PACKAGE, callingPackage) .set(MediaMetrics.Property.CLIENT_NAME, caller) .set(MediaMetrics.Property.DIRECTION, direction > 0 ? MediaMetrics.Value.UP : MediaMetrics.Value.DOWN) .set(MediaMetrics.Property.EXTERNAL, hasExternalVolumeController ? MediaMetrics.Value.YES : MediaMetrics.Value.NO) .set(MediaMetrics.Property.FLAGS, flags) .record(); if (hasExternalVolumeController) { return; } final int streamType; synchronized (mForceControlStreamLock) { // Request lock in case mVolumeControlStream is changed by other thread. if (mUserSelectedVolumeControlStream) { // implies mVolumeControlStream != -1 streamType = mVolumeControlStream; } else { // 这里获取到,可能是活动状态的音频流,但是不确定,还有待进一步确认 final int maybeActiveStreamType = getActiveStreamType(suggestedStreamType); final boolean activeForReal; if (maybeActiveStreamType == AudioSystem.STREAM_RING || maybeActiveStreamType == AudioSystem.STREAM_NOTIFICATION) { activeForReal = wasStreamActiveRecently(maybeActiveStreamType, 0); } else { activeForReal = AudioSystem.isStreamActive(maybeActiveStreamType, 0); } if (activeForReal || mVolumeControlStream == -1) { streamType = maybeActiveStreamType; } else { // activeForReal为false并且mVolumeControlStream不为-1 // 表示用户点击了音量进度条,这时候要操作修改的流类型为mVolumeControlStream对应的流类型 streamType = mVolumeControlStream; } } } final boolean isMute = isMuteAdjust(direction); // 确保我们获取到的流类型是有效的 ensureValidStreamType(streamType); // 将我们获取到的流,进行流映射,拿到最终需要操作的流类型 final int resolvedStream = mStreamVolumeAlias[streamType]; // Play sounds on STREAM_RING only. if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && resolvedStream != AudioSystem.STREAM_RING) { flags &= ~AudioManager.FLAG_PLAY_SOUND; } // For notifications/ring, show the ui before making any adjustments // Don't suppress mute/unmute requests // Don't suppress adjustments for single volume device // 通知和响铃,调整音量之前先显示UI。 if (mVolumeController.suppressAdjustment(resolvedStream, flags, isMute) && !mIsSingleVolume) { direction = 0; flags &= ~AudioManager.FLAG_PLAY_SOUND; flags &= ~AudioManager.FLAG_VIBRATE; if (DEBUG_VOL) Log.d(TAG, "Volume controller suppressed adjustment"); } // 这里设置音量 adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid, hasModifyAudioSettings, keyEventMode); }
suppressAdjustment:字面意思为抑制调整,为什么抑制调整呢,说白了,当我们没有显示音量的UI进度条的时候,不管我们是加音量还是减音量(注意:静音和解静音除外),这个时候都是先显示音量条,而不去改变音量的大小。所以当这个方法返回true的时候, direction = 0,这里direction为0就表示我们的操作为ADJUST_SAME,大家可以在AudioManager里面查看ADJUST_SAME的注释就知道这个操作表示只弹出UI但是不调整音量大小。
③adjustStreamVolume
protected void adjustStreamVolume(int streamType, int direction, int flags, String callingPackage, String caller, int uid, boolean hasModifyAudioSettings, int keyEventMode) { //mUseFixedVolume表示使用固定音量,我们无法修改音量 if (mUseFixedVolume) { return; } if (DEBUG_VOL) Log.d(TAG, "adjustStreamVolume() stream=" + streamType + ", dir=" + direction + ", flags=" + flags + ", caller=" + caller); ensureValidDirection(direction); ensureValidStreamType(streamType); boolean isMuteAdjust = isMuteAdjust(direction); if (isMuteAdjust && !isStreamAffectedByMute(streamType)) { return; } // If adjust is mute and the stream is STREAM_VOICE_CALL or STREAM_BLUETOOTH_SCO, make sure // that the calling app have the MODIFY_PHONE_STATE permission. if (isMuteAdjust && (streamType == AudioSystem.STREAM_VOICE_CALL || streamType == AudioSystem.STREAM_BLUETOOTH_SCO) && mContext.checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: adjustStreamVolume from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); return; } // If the stream is STREAM_ASSISTANT, // make sure that the calling app have the MODIFY_AUDIO_ROUTING permission. if (streamType == AudioSystem.STREAM_ASSISTANT && mContext.checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_AUDIO_ROUTING) != PackageManager.PERMISSION_GRANTED) { Log.w(TAG, "MODIFY_AUDIO_ROUTING Permission Denial: adjustStreamVolume from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); return; } // use stream type alias here so that streams with same alias have the same behavior, // including with regard to silent mode control (e.g the use of STREAM_RING below and in // checkForRingerModeChange() in place of STREAM_RING or STREAM_NOTIFICATION) //进行音频流的映射,拿到映射后的音频流 int streamTypeAlias = mStreamVolumeAlias[streamType]; //mStreamStates是一个存储VolumeStreamState类型的数组,保存着每个音频流的状态。 //VolumeStreamState是AudioService的一个内部类,里面保存单个音频流的所有信息,比如流类型,音量大小,mute状态等。 //并且相同的流类型,在不同的设备,大小也是不一样的(比如耳机和扬声器,媒体音量大小是不一样的) //这也是在VolumeStreamState里面去维护的。 VolumeStreamState streamState = mStreamStates[streamTypeAlias]; final int device = getDeviceForStream(streamTypeAlias); int aliasIndex = streamState.getIndex(device); boolean adjustVolume = true; int step; // skip a2dp absolute volume control request when the device // is not an a2dp device if (!AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device) && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) { return; } // If we are being called by the system (e.g. hardware keys) check for current user // so we handle user restrictions correctly. if (uid == android.os.Process.SYSTEM_UID) { uid = UserHandle.getUid(getCurrentUserId(), UserHandle.getAppId(uid)); } if (mAppOps.noteOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage) != AppOpsManager.MODE_ALLOWED) { return; } // reset any pending volume command // 清除掉任何待处理的音量命令 synchronized (mSafeMediaVolumeStateLock) { mPendingVolumeCommand = null; } // 表示不是固定音量 flags &= ~AudioManager.FLAG_FIXED_VOLUME; // 如果是多媒体音量,并且是使用固定音量的设备 if (streamTypeAlias == AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) { // 加上表示固定音量的flag flags |= AudioManager.FLAG_FIXED_VOLUME; // Always toggle between max safe volume and 0 for fixed volume devices where safe // volume is enforced, and max and 0 for the others. // This is simulated by stepping by the full allowed volume range if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE && mSafeMediaVolumeDevices.contains(device)) { step = safeMediaVolumeIndex(device); } else { step = streamState.getMaxIndex(); } if (aliasIndex != 0) { aliasIndex = step; } } else { // convert one UI step (+/-1) into a number of internal units on the stream alias // 如果不是多媒体音量,或者是多媒体音量但是不是固定音量的设备时 // 将音量值的步进量从源流类型变换到目标流类型下,由于不同的流类型的音量调节范围不同,所以这个转换是必需的 step = rescaleStep(10, streamType, streamTypeAlias); } // // 情景模式的处理 // If either the client forces allowing ringer modes for this adjustment, // or the stream type is one that is affected by ringer modes if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || (streamTypeAlias == getUiSoundsStreamType())) { int ringerMode = getRingerModeInternal(); // do not vibrate if already in vibrate mode // 如果已经是震动模式,则不进行震动 if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) { flags &= ~AudioManager.FLAG_VIBRATE; } // Check if the ringer mode handles this adjustment. If it does we don't // need to adjust the volume further. // 根据我们的操作来检查是否需要切换情景模式 final int result = checkForRingerModeChange(aliasIndex, direction, step, streamState.mIsMuted, callingPackage, flags); adjustVolume = (result & FLAG_ADJUST_VOLUME) != 0; // If suppressing a volume adjustment in silent mode, display the UI hint if ((result & AudioManager.FLAG_SHOW_SILENT_HINT) != 0) { flags |= AudioManager.FLAG_SHOW_SILENT_HINT; } // If suppressing a volume down adjustment in vibrate mode, display the UI hint if ((result & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0) { flags |= AudioManager.FLAG_SHOW_VIBRATE_HINT; } } // If the ringer mode or zen is muting the stream, do not change stream unless // it'll cause us to exit dnd // 勿扰模式 if (!volumeAdjustmentAllowedByDnd(streamTypeAlias, flags)) { adjustVolume = false; } // 获取旧的音量大小 int oldIndex = mStreamStates[streamType].getIndex(device); if (adjustVolume && (direction != AudioManager.ADJUST_SAME) && (keyEventMode != VOL_ADJUST_END)) { mAudioHandler.removeMessages(MSG_UNMUTE_STREAM); // 先处理静音调整 if (isMuteAdjust) { boolean state; if (direction == AudioManager.ADJUST_TOGGLE_MUTE) { state = !streamState.mIsMuted; } else { state = direction == AudioManager.ADJUST_MUTE; } if (streamTypeAlias == AudioSystem.STREAM_MUSIC) { setSystemAudioMute(state); } for (int stream = 0; stream < mStreamStates.length; stream++) { if (streamTypeAlias == mStreamVolumeAlias[stream]) { if (!(readCameraSoundForced() && (mStreamStates[stream].getStreamType() == AudioSystem.STREAM_SYSTEM_ENFORCED))) { // 这里获取当前流对应的VolumeStreamState实例,然后去调用mute方法 // 最终也会到AudioSystem去调用native方法 mStreamStates[stream].mute(state); } } } } else if ((direction == AudioManager.ADJUST_RAISE) && !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) { // 安全音量提示,音量增加的时候才会去检测 Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex); mVolumeController.postDisplaySafeVolumeWarning(flags); } else if (!isFullVolumeDevice(device) && (streamState.adjustIndex(direction * step, device, caller, hasModifyAudioSettings) || streamState.mIsMuted)) { // Post message to set system volume (it in turn will post a // message to persist). if (streamState.mIsMuted) { // Unmute the stream if it was previously muted if (direction == AudioManager.ADJUST_RAISE) { // unmute immediately for volume up streamState.mute(false); } else if (direction == AudioManager.ADJUST_LOWER) { if (mIsSingleVolume) { sendMsg(mAudioHandler, MSG_UNMUTE_STREAM, SENDMSG_QUEUE, streamTypeAlias, flags, null, UNMUTE_STREAM_DELAY); } } } // 设置音量到底层 sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, device, 0, streamState, 0); } int newIndex = mStreamStates[streamType].getIndex(device); // Check if volume update should be send to AVRCP if (streamTypeAlias == AudioSystem.STREAM_MUSIC && AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device) && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { if (DEBUG_VOL) { Log.d(TAG, "adjustSreamVolume: postSetAvrcpAbsoluteVolumeIndex index=" + newIndex + "stream=" + streamType); } mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(newIndex / 10); } // Check if volume update should be send to Hearing Aid if (device == AudioSystem.DEVICE_OUT_HEARING_AID) { // only modify the hearing aid attenuation when the stream to modify matches // the one expected by the hearing aid if (streamType == getHearingAidStreamType()) { if (DEBUG_VOL) { Log.d(TAG, "adjustSreamVolume postSetHearingAidVolumeIndex index=" + newIndex + " stream=" + streamType); } mDeviceBroker.postSetHearingAidVolumeIndex(newIndex, streamType); } } // Check if volume update should be sent to Hdmi system audio. if (streamTypeAlias == AudioSystem.STREAM_MUSIC) { setSystemAudioVolume(oldIndex, newIndex, getStreamMaxVolume(streamType), flags); } } final int newIndex = mStreamStates[streamType].getIndex(device); if (adjustVolume) { synchronized (mHdmiClientLock) { if (mHdmiManager != null) { // mHdmiCecSink true => mHdmiPlaybackClient != null if (mHdmiCecSink && mHdmiCecVolumeControlEnabled && streamTypeAlias == AudioSystem.STREAM_MUSIC // vol change on a full volume device && isFullVolumeDevice(device)) { int keyCode = KeyEvent.KEYCODE_UNKNOWN; switch (direction) { case AudioManager.ADJUST_RAISE: keyCode = KeyEvent.KEYCODE_VOLUME_UP; break; case AudioManager.ADJUST_LOWER: keyCode = KeyEvent.KEYCODE_VOLUME_DOWN; break; case AudioManager.ADJUST_TOGGLE_MUTE: keyCode = KeyEvent.KEYCODE_VOLUME_MUTE; break; default: break; } if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { final long ident = Binder.clearCallingIdentity(); try { final long time = java.lang.System.currentTimeMillis(); switch (keyEventMode) { case VOL_ADJUST_NORMAL: mHdmiPlaybackClient.sendVolumeKeyEvent(keyCode, true); mHdmiPlaybackClient.sendVolumeKeyEvent(keyCode, false); break; case VOL_ADJUST_START: mHdmiPlaybackClient.sendVolumeKeyEvent(keyCode, true); break; case VOL_ADJUST_END: mHdmiPlaybackClient.sendVolumeKeyEvent(keyCode, false); break; default: Log.e(TAG, "Invalid keyEventMode " + keyEventMode); } } finally { Binder.restoreCallingIdentity(ident); } } } if (streamTypeAlias == AudioSystem.STREAM_MUSIC && (oldIndex != newIndex || isMuteAdjust)) { maybeSendSystemAudioStatusCommand(isMuteAdjust); } } } } // 通知外界音量发生变化 sendVolumeUpdate(streamType, oldIndex, newIndex, flags, device); }
④sendVolumeUpdate
// UI update and Broadcast Intent protected void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags, int device) { streamType = mStreamVolumeAlias[streamType]; if (streamType == AudioSystem.STREAM_MUSIC) { flags = updateFlagsForTvPlatform(flags); if (isFullVolumeDevice(device)) { flags &= ~AudioManager.FLAG_SHOW_UI; } } mVolumeController.postVolumeChanged(streamType, flags); }
3、AudioManager
/** * Increase the ringer volume. * * @see #adjustVolume(int, int) * @see #adjustStreamVolume(int, int, int) */ public static final int ADJUST_RAISE = 1; /** * Decrease the ringer volume. * * @see #adjustVolume(int, int) * @see #adjustStreamVolume(int, int, int) */ public static final int ADJUST_LOWER = -1; /** * Maintain the previous ringer volume. This may be useful when needing to * show the volume toast without actually modifying the volume. * * @see #adjustVolume(int, int) * @see #adjustStreamVolume(int, int, int) */ public static final int ADJUST_SAME = 0; /** * Mute the volume. Has no effect if the stream is already muted. * * @see #adjustVolume(int, int) * @see #adjustStreamVolume(int, int, int) */ public static final int ADJUST_MUTE = -100; /** * Unmute the volume. Has no effect if the stream is not muted. * * @see #adjustVolume(int, int) * @see #adjustStreamVolume(int, int, int) */ public static final int ADJUST_UNMUTE = 100; /** * Toggle the mute state. If muted the stream will be unmuted. If not muted * the stream will be muted. * * @see #adjustVolume(int, int) * @see #adjustStreamVolume(int, int, int) */ public static final int ADJUST_TOGGLE_MUTE = 101;