栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

SystemUI介绍

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

SystemUI介绍

1.启动流程

SystemUI启动是在SystemServer进程之后启动的,android系统启动流程依次是:

从Boot RAM->BootLoader->Kenel->Init->Zygote->SystemServer->Launcher,SystemUI是在SystemServer进程中启动的,SystemServer是Zygote进程fork出来的,SystemServer.java文件查找路径方法,切换到工程目录下,

find ./ -name *SystemServer.java*

找到SystemServer之后,SystemServer的main方法如下:

//文件路径:
//./frameworks/base/services/java/com/android/server/SystemServer.java

public static void main(String[] args) {
    new SystemServer().run();
}

这里面直接初始化后,调用run方法,在run方法里面启动的有三个Service的方法

// Start services.
try {
    t.traceBegin("StartServices");
    startBootstrapServices(t);//启动引导服务
    startCoreServices(t);//启动核心服务
    startOtherServices(t);//启动其他服务
} catch (Throwable ex) {
    Slog.e("System", "******************************************");
    Slog.e("System", "************ Failure starting system services", ex);
    throw ex;
} finally {
    t.traceEnd(); // StartServices
}
 

SystemUIService的启动是在startOtherService里面,

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);
    context.startServiceAsUser(intent, UserHandle.SYSTEM);
    windowManager.onSystemUiStarted();
}

从localService里面拿到Service的名字,然后启动,拿到的Service名字是SystemUIService,SystemUIService执行onCreate方法,

//文件路径:
//./frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIService.java
// Start all of SystemUI
((SystemUIApplication) getApplication()).startServicesIfNeeded();
​
// Finish initializing dump logic
mLogBufferFreezer.attach(mBroadcastDispatcher);
​
// For debugging RescueParty
if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.crash_sysui", false)) {
    throw new RuntimeException();
}
​
if (Build.IS_DEBUGGABLE) {
    // b/71353150 - looking for leaked binder proxies
    BinderInternal.nSetBinderProxyCountEnabled(true);
    BinderInternal.nSetBinderProxyCountWatermarks(1000,900);
    BinderInternal.setBinderProxyCountCallback(
    new BinderInternal.BinderProxyLimitListener() {
        @Override
        public void onLimitReached(int uid) {
        Slog.w(SystemUIApplication.TAG,
        "uid " + uid + " sent too many Binder proxies to uid "
        + Process.myUid());
        }
    }, mMainHandler);
}
​
// Bind the dump service so we can dump extra info during a bug report
startServiceAsUser(
new Intent(getApplicationContext(), SystemUIAuxiliaryDumpService.class),
UserHandle.SYSTEM);

前面是SystemUIService启动的方法,后面都是调试状态下的输出,等于是直接调用SystemUIApplication.java的startServicesIfNeeded方法,

//文件路径:
//./frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java

​
public void startServicesIfNeeded() {
    String[] names = SystemUIFactory.getInstance().getSystemUIServiceComponents(getResources());
    startServicesIfNeeded( "StartServices", names);
}

这个Service的列表是从SystemUIFactory里面取到的,

//文件路径
//./frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java

public String[] getSystemUIServiceComponents(Resources resources) {
    return resources.getStringArray(R.array.config_systemUIServiceComponents);
}

从res里面的array里面拿到需要启动的服务,这个服务的数组分为两种,一种是正常的res里面的value,另外一种是tv版的服务数组列表,内容如下:

//文件路径:
//frameworks/base/packages/SystemUI/res/values-television/config.xml


com.android.systemui.util.NotificationChannels//通知
com.android.systemui.volume.VolumeUI//声音
com.android.systemui.stackdivider.Divider//分屏
com.android.systemui.statusbar.tv.TvStatusBar//电视状态栏
com.android.systemui.usb.StorageNotification//存储通知
com.android.systemui.power.PowerUI//电量界面
com.android.systemui.media.RingtonePlayer//铃声播放器
com.android.systemui.keyboard.KeyboardUI//键盘界面
com.android.systemui.pip.PipUI//图片窗口中的图片
com.android.systemui.shortcut.ShortcutKeyDispatcher//快捷分发器
@string/config_systemUIVendorServiceComponent//供应商服务
com.android.systemui.SliceBroadcastRelayHandler//允许打开设置app
com.android.systemui.SizeCompatModeActivityController//允许重启activity
com.android.systemui.statusbar.notification.InstantAppNotifier//即时应用程序通知
com.android.systemui.toast.ToastUI//弹窗提示

需要启动的服务大概如上述所示,后面详细说明这些服务,

//文件路径:
//./frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
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;
        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]);
            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");
            }
            //如果android系统已经启动的话,会有回调
            if (mBootCompleteCache.isBootComplete()) {
                mServices[i].onBootCompleted();
            }
​
            //这里是注册dump,用来使用命令dump时使用
            dumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);
        }
        mRootComponent.getInitController().executePostInitTasks();
        log.traceEnd();
​
        mServicesStarted = true;
    }

在这个方法里面启动SystemUI的相关内容的,把存放进数组的SystemUI通过反射的形式初始化对象,然后每个都调用start方法启动,启动相关的SystemUI内容,之后把这些可以服务都通过dumpManager注册dump的服务,启动流程大致如此。

2.注册dump介绍

之前的startServicesIfNeeded方法最后一行里面进行了注册,注册的代码如下:

//文件路径
//./frameworks/base/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt

@Synchronized
fun registerDumpable(name: String, module: Dumpable) {
    if (!canAssignTonameLocked(name, module)) {
        throw IllegalArgumentException("'$name' is already registered")
    }
​
    dumpables[name] = RegisteredDumpable(name, module)
}

这个里面存放的是MutableMap>类型的map数据,其中key在存储时表示的SystemUI的className,里面记录的是config.xml里面service,这些service的顶层父类都是SystemUI,SystemUI是一个实现了Dumpable接口的抽象类,这个map的value是这些service的实体对象,就是SystemUIApplication中通过Provider中拿到的对象。

其中Dump接口代码如下:

//文件路径
//./frameworks/base/services/core/java/com/android/server/soundtrigger_middleware/Dumpable.java

public interface Dumpable {
​
    
    void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args);
}

代码注释中已经说明,实现这个接口主要是为了通过“adb shell dumpsys activity service com.android.systemui”这个命令dump内容,可以通过cmd控制台输出这个命令看一下输出,这个输出会根据子类重写的这个方法把相关的参数输出出来,从过滤出来的日志能够查到。

3.SystemUI不同的UI内容 1.简述

SystemUI是顶层父类,实现了Dumpable接口用于dump相关内容,其他所有的SystemUI全部都是继承自这个类,

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) {
    }
​
    //需要dump时使用
    @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);
    }
}
2.SystemUI分类

2.1通知管理:NotificationChannels

这个类是顶层通知栏类似于推送消息时,展示的内容,调用这个start方法之后,把需要用到的channelID创建出来,

final NotificationManager nm = context.getSystemService(NotificationManager.class);
final NotificationChannel batteryChannel = new NotificationChannel(BATTERY,
                                                                   context.getString(R.string.notification_channel_battery),
                                                                   NotificationManager.importANCE_MAX);
final String soundPath = Settings.Global.getString(context.getContentResolver(),
                                                   Settings.Global.LOW_BATTERY_SOUND);
batteryChannel.setSound(Uri.parse("file://" + soundPath), new AudioAttributes.Builder()
                        .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                        .setUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT)
                        .build());
batteryChannel.setBlockable(true);
​
final NotificationChannel alerts = new NotificationChannel(
    alertS,
    context.getString(R.string.notification_channel_alerts),
    NotificationManager.importANCE_HIGH);
​
final NotificationChannel general = new NotificationChannel(
    GENERAL,
    context.getString(R.string.notification_channel_general),
    NotificationManager.importANCE_MIN);
​
final NotificationChannel storage = new NotificationChannel(
    STORAGE,
    context.getString(R.string.notification_channel_storage),
    isTv(context)
    ? NotificationManager.importANCE_DEFAULT
    : NotificationManager.importANCE_LOW);
​
final NotificationChannel hint = new NotificationChannel(
    HINTS,
    context.getString(R.string.notification_channel_hints),
    NotificationManager.importANCE_DEFAULT);
// No need to bypass DND.
​
nm.createNotificationChannels(Arrays.asList(
    alerts,
    general,
    storage,
    createScreenshotChannel(
        context.getString(R.string.notification_channel_screenshot),
        nm.getNotificationChannel(SCREENSHOTS_LEGACY)),
    batteryChannel,
    hint
));
​
// Delete older SS channel if present.
// Screenshots promoted to heads-up in P, this cleans up the lower priority channel from O.
// This line can be deleted in Q.
nm.deleteNotificationChannel(SCREENSHOTS_LEGACY);
​
​
if (isTv(context)) {
    // TV specific notification channel for TV PIP controls.
    // importance should be {@link NotificationManager#importANCE_MAX} to have the highest
    // priority, so it can be shown in all times.
    nm.createNotificationChannel(new NotificationChannel(
        TVPIP,
        context.getString(R.string.notification_channel_tv_pip),
        NotificationManager.importANCE_MAX));
}

分别创建了电量,alerts,普通类型的消息,存储,hint,截屏,TVPIP的通知渠道,只是创建好,但是什么时候通知调用?

2.2音量UI

VolumeUI.java 是控制音量弹出框的,调用start方法后,设置是否可用的开关和警告提示的开关,主要流程涉及到的类包括:VolumeDialogComponent.java,VolumeDialog.java,VolumeDialogImpl.java,VolumeDialogController.java,VolumeDialogControllerImpl.java,AudioManager.java,大概流程如下,VolumeDialogComponent是一个dialog的组件,

@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)
        .withDefault(this::createDefault)//调用这个方法初始化
        .withCallback(dialog -> {
            if (mDialog != null) {
                mDialog.destroy();
            }
            mDialog = dialog;
            mDialog.init(LayoutParams.TYPE_VOLUME_OVERLAY, mVolumeDialogCallback);//init设置一些内容
        }).build();
    applyConfiguration();
    Dependency.get(TunerService.class).addTunable(this, VOLUME_DOWN_SILENT, VOLUME_UP_SILENT,
                                                  VOLUME_SILENT_DO_NOT_DISTURB);
}

调用create创建dialog,

protected VolumeDialog createDefault() {
    VolumeDialogImpl impl = new VolumeDialogImpl(mContext);
    impl.setStreamimportant(AudioManager.STREAM_SYSTEM, false);
    impl.setAutomute(true);
    impl.setSilentMode(false);
    return impl;
}

创建出来一个VolumeDialogImpl对象,VolumeDialogImpl是VolumeDialog接口的实现类,然后调用dialog的初始化,初始化的代码如下,

public void init(int windowType, Callback callback) {
    initDialog();
​
    mAccessibility.init();
​
    mController.addCallback(mControllerCallbackH, mHandler);
    mController.getState();
​
    Dependency.get(ConfigurationController.class).addCallback(this);
}

其中initDialog()方法是调用这个dialog初始化的内容,这个里面是比较典型的dialog开发时的代码,不过也非常复杂,setContentView时传入的是R.layout.volume_dialog,这个layout是一个frameLayout和LinearLayout的组合,其中比较重要的地方,


                    
            

这里面会把不同音量控制的开关添加到这个linearLayout里面,在initDialog中最终会调用addRow()方法,根据是否是单声道,如果不是单声道的话会添加很多的View到这个linearLayout里面,分为STREAM_ACCESSIBILITY,STREAM_MUSIC(音乐大小),STREAM_RING(铃声),STREAM_ALARM(系统铃声),STREAM_VOICE_CALL(通话声音),STREAM_BLUETOOTH_SCO(蓝牙,这个不能用),STREAM_SYSTEM(系统声音),这些需要控制的音量全部都会通过addRow()添加进来,addRow()方法如下,

private void addRow(int stream, int iconRes, int iconMuteRes, boolean important,
                    boolean defaultStream, boolean dynamic) {
    if (D.BUG) Slog.d(TAG, "Adding row for stream " + stream);
    VolumeRow row = new VolumeRow();
    initRow(row, stream, iconRes, iconMuteRes, important, defaultStream);
    mDialogRowsView.addView(row.view);
    mRows.add(row);
}

这个里面会先调用初始化,其中VolumeRow这个类是个组合类,他是对这隔单个音量的信息的封装,

    
private static class VolumeRow {
        private View view;
        private TextView header;
        private ImageButton icon;
        private SeekBar slider;
        private int stream;
        private StreamState ss;
        private long userAttempt;  // last user-driven slider change
        private boolean tracking;  // tracking slider touch
        private int requestedLevel = -1;  // pending user-requested level via progress changed
        private int iconRes;
        private int iconMuteRes;
        private boolean important;
        private boolean defaultStream;
        private ColorStateList cachedTint;
        private int iconState;  // from Events
        private ObjectAnimator anim;  // slider progress animation for non-touch-related updates
        private int animTargetProgress;
        private int lastAudibleLevel = 1;
        private frameLayout dndIcon;
    }

在调用init初始化时,row中的view通过inflate(R.layout.volume_dialog_row)的形式构造一个View,xml里面有seekBar,这个SeekBar会注册监听器,是通过VolumeSeekBarChangeListener这个内部类实现的,具体回调的内容包括触摸反馈,状态改变,然后做相应的处理,然后动态的添加到volume_dialog_rows指定的viewGroup里面,还需要有一个List mRows的对象控制添加的内容,这部分大概是音量UI的View层面的内容,音量这部分内容使用的是MVP的架构,这个dialog属于View层面的内容。

VolumeDialogControllerImpl是Presenter层面的内容,这个类是VolumeDialogController接口的实现,也实现了dump接口用于dump需要输出的内容,在初始化时拿到AudioManager的服务,然后互相通信,AudioManager是音量的具体Model层,他负责音量数据源,他们之间的通信关系如下,

2.3Divider分屏

Divider是处理分屏的业务,构造方法如下:

public Divider(Context context, Optional> recentsOptionalLazy,
               DisplayController displayController, SystemWindows systemWindows,
               DisplayImeController imeController, Handler handler,
               KeyguardStateController keyguardStateController, TransactionPool transactionPool) {
    super(context);
    mDisplayController = displayController;
    mSystemWindows = systemWindows;//这个参数是当前的window,通过他处理添加分屏,删除分屏的操作
    mImeController = imeController;
    mHandler = handler;
    mKeyguardStateController = keyguardStateController;
    mRecentsOptionalLazy = recentsOptionalLazy;
    mForcedResizableController = new ForcedResizableInfoActivityController(context, this);
    mTransactionPool = transactionPool;
    mWindowManagerProxy = new WindowManagerProxy(mTransactionPool, mHandler);
    mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mHandler);
}

其中mSystemWindows是一个SystemWindows类的对象,主要就是通过他来控制分屏处理的,start方法如下:

@Override
public void start() {
    mWindowManager = new DividerWindowManager(mSystemWindows);
    mDisplayController.addDisplayWindowListener(this);
    // Hide the divider when keyguard is showing. Even though keyguard/statusbar is above
    // everything, it is actually transparent except for notifications, so we still need to
    // hide any surfaces that are below it.
    // TODO(b/148906453): Figure out keyguard dismiss animation for divider view.
    mKeyguardStateController.addCallback(new KeyguardStateController.Callback() {
        @Override
        public void onUnlockedChanged() {
​
        }
​
        @Override
        public void onKeyguardShowingChanged() {
            if (!isSplitActive() || mView == null) {
                return;
            }
            mView.setHidden(mKeyguardStateController.isShowing());
            if (!mKeyguardStateController.isShowing()) {
                mImePositionProcessor.updateAdjustForIme();
            }
        }
​
        @Override
        public void onKeyguardFadingAwayChanged() {
​
        }
    });
    // Don't initialize the divider or anything until we get the default display.
}

start方法中构造了一个DividerWindowManager的对象,分屏通过这个windowManager操作添加屏幕,或者删除屏幕的操作,这里面还加了一个键盘的回调,从注释上的意思是说隐藏分屏当键盘展示的时候,真正负责处理添加删除的是DividerWindowManager,DividerWindowManager的构造方法入参是mSystemWindows,Divider类中方法比较多,其中addDivider()和removeDivider()的代码如下:

private void addDivider(Configuration configuration) {
    Context dctx = mDisplayController.getDisplayContext(mContext.getDisplayId());
    mView = (DividerView)
        LayoutInflater.from(dctx).inflate(R.layout.docked_stack_divider, null);
    DisplayLayout displayLayout = mDisplayController.getDisplayLayout(mContext.getDisplayId());
    mView.injectDependencies(mWindowManager, mDividerState, this, mSplits, mSplitLayout,
                             mImePositionProcessor, mWindowManagerProxy);
    mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE);
    mView.setMinimizedDockStack(mMinimized, mHomeStackResizable, null );
    final int size = dctx.getResources().getDimensionPixelSize(
        com.android.internal.R.dimen.docked_stack_divider_thickness);
    final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
    final int width = landscape ? size : displayLayout.width();
    final int height = landscape ? displayLayout.height() : size;
    mWindowManager.add(mView, width, height, mContext.getDisplayId());
}
​
private void removeDivider() {
    if (mView != null) {
        mView.onDividerRemoved();
    }
    mWindowManager.remove();
}

其实都是通过windowManager进行的操作,每次add都是inflate出来一个view,通过windowManager添加进去,每次操作时都是先removeDivider然后再重新addDivider(),而removeDivider()操作在多处调用,其中一处如下:

private void update(Configuration configuration) {
    final boolean isDividerHidden = mView != null && mKeyguardStateController.isShowing();
​
    removeDivider();
    addDivider(configuration);
​
    if (mMinimized) {
        mView.setMinimizedDockStack(true, mHomeStackResizable, null );
        updateTouchable();
    }
    mView.setHidden(isDividerHidden);
}

先删除,再添加,其中操作这个VIew的时候,这个VIew的名字是DividerView,这个是个自定义View,里面处理拖拽,触摸等一系列操作。

2.4TvStatusBar电视状态栏

TvStatusBar也是一个状态栏的通知,主要处理隐式意图跳转的逻辑,start方法如下:

@Override
public void start() {
    final IStatusBarService barService = IStatusBarService.Stub.asInterface(
        ServiceManager.getService(Context.STATUS_BAR_SERVICE));
    mCommandQueue.addCallback(this);
    try {
        barService.registerStatusBar(mCommandQueue);
    } catch (RemoteException ex) {
        // If the system process isn't there we're doomed anyway.
    }
}

这里面进行了一个绑定服务,添加了一个回调,其中mCommandQueue是构造方法传递的参数,添加完回调后主要是为了方便通知,mCommandQueue是CommandQueue.java的实例对象,这个类比较庞大,里面有一个名为H的handler用于发送消息,TvStatusBar实现的接口是CommandQueue.Callbacks,这个类就是CommandQueue.java的一个接口,定义了非常多的方法用于循环通知回调,列举一部分大概确认下内容:

default void setIcon(String slot, StatusBarIcon icon) { }
default void removeIcon(String slot) { }
​

default void disable(int displayId, @DisableFlags int state1, @Disable2Flags int state2,
                     boolean animate) { }
default void animateExpandNotificationsPanel() { }
default void animateCollapsePanels(int flags, boolean force) { }
default void togglePanel() { }
default void animateExpandSettingsPanel(String obj) { }
​

default void setImeWindowStatus(int displayId, IBinder token,  int vis,
                                @BackDispositionMode int backDisposition, boolean showImeSwitcher) { }

而TvStatusBar只实现了其中两个方法,分别是是animateExpandNotificationsPanel()和startAssist(),animateExpandNotificationsPanel用于启动系统的activity,startAssist用于打开assist里面的内容。在CommandQueue中的H中调用animateExpandNotificationsPanel方法,

case MSG_EXPAND_NOTIFICATIONS:
for (int i = 0; i < mCallbacks.size(); i++) {
    mCallbacks.get(i).animateExpandNotificationsPanel();
}

发现这个MSG_EXPAND_NOTIFICATIONS类型的消息调用的方法在本模块没有被调用到:

public void animateExpandNotificationsPanel() {
    synchronized (mLock) {
        mHandler.removeMessages(MSG_EXPAND_NOTIFICATIONS);
        mHandler.sendEmptyMessage(MSG_EXPAND_NOTIFICATIONS);
    }
}

2.5StorageNotification存储通知

StorageNotification.java主要是处理存储相关的通知,其中start方法的内容如下:

@Override
public void start() {
    mNotificationManager = mContext.getSystemService(NotificationManager.class);//获取通知服务
​
    mStorageManager = mContext.getSystemService(StorageManager.class);
    mStorageManager.registerListener(mListener);//这里注册了一个监听器
​
    mContext.registerReceiver(mSnoozeReceiver, new IntentFilter(ACTION_SNOOZE_VOLUME),
                              android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null);//
    mContext.registerReceiver(mFinishReceiver, new IntentFilter(ACTION_FINISH_WIZARD),
                              android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null);//完成的广播
​
    // Kick current state into place
    final List disks = mStorageManager.getDisks();
    for (DiskInfo disk : disks) {
        onDiskScannedInternal(disk, disk.volumeCount);
    }
​
    final List vols = mStorageManager.getVolumes();
    for (VolumeInfo vol : vols) {
        onVolumeStateChangedInternal(vol);
    }
​
    mContext.getPackageManager().registerMoveCallback(mMoveCallback, new Handler());//这里也加了个回调
​
    updateMissingPrivateVolumes();
}

主要看注册的监听器,其中mListener是个检测存储状态改变的回调类,

private final StorageEventListener mListener = new StorageEventListener() {
    @Override
    public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
        onVolumeStateChangedInternal(vol);
    }
​
    @Override
    public void onVolumeRecordChanged(VolumeRecord rec) {
        // Avoid kicking notifications when getting early metadata before
        // mounted. If already mounted, we're being kicked because of a
        // nickname or init'ed change.
        final VolumeInfo vol = mStorageManager.findVolumeByUuid(rec.getFsUuid());
        if (vol != null && vol.isMountedReadable()) {
            onVolumeStateChangedInternal(vol);
        }
    }
​
    @Override
    public void onVolumeForgotten(String fsUuid) {
        // Stop annoying the user
        mNotificationManager.cancelAsUser(fsUuid, SystemMessage.NOTE_STORAGE_PRIVATE,
                                          UserHandle.ALL);
    }
​
    @Override
    public void onDiskScanned(DiskInfo disk, int volumeCount) {
        onDiskScannedInternal(disk, volumeCount);
    }
​
    @Override
    public void onDiskDestroyed(DiskInfo disk) {
        onDiskDestroyedInternal(disk);
    }
};

状态改变的时候会调用onVolumeStateChangedInternal这个方法,继续调用:

private void onVolumeStateChangedInternal(VolumeInfo vol) {
    switch (vol.getType()) {
        case VolumeInfo.TYPE_PRIVATE:
            onPrivateVolumeStateChangedInternal(vol);
            break;
        case VolumeInfo.TYPE_PUBLIC:
            onPublicVolumeStateChangedInternal(vol);
            break;
    }
}

继续调用onPublicVolumeStateChangedInternal,在这个方法里面会处理各种不同状态的通知:

private void onPublicVolumeStateChangedInternal(VolumeInfo vol) {
    Log.d(TAG, "Notifying about public volume: " + vol.toString());
​
    // Volume state change event may come from removed user, in this case, mountedUserId will
    // equals to UserHandle.USER_NULL (-10000) which will do nothing when call cancelAsUser(),
    // but cause crash when call notifyAsUser(). Here we return directly for USER_NULL, and
    // leave all notifications belong to removed user to NotificationManagerService, the latter
    // will remove all notifications of the removed user when handles user stopped broadcast.
    if (isAutomotive() && vol.getMountUserId() == UserHandle.USER_NULL) {
        Log.d(TAG, "Ignore public volume state change event of removed user");
        return;
    }
​
    final Notification notif;
    switch (vol.getState()) {
        case VolumeInfo.STATE_UNMOUNTED:
            //卸载
            notif = onVolumeUnmounted(vol);
            break;
        case VolumeInfo.STATE_CHECKING:
            //检测
            notif = onVolumeChecking(vol);
            break;
        case VolumeInfo.STATE_MOUNTED:
        case VolumeInfo.STATE_MOUNTED_READ_ONLY:
            //挂载
            notif = onVolumeMounted(vol);
            break;
        case VolumeInfo.STATE_FORMATTING:
            //对其
            notif = onVolumeFormatting(vol);
            break;
        case VolumeInfo.STATE_EJECTING:
            //拒绝
            notif = onVolumeEjecting(vol);
            break;
        case VolumeInfo.STATE_UNMOUNTABLE:
            //不可被卸载的
            notif = onVolumeUnmountable(vol);
            break;
        case VolumeInfo.STATE_REMOVED:
            //移除
            notif = onVolumeRemoved(vol);
            break;
        case VolumeInfo.STATE_BAD_REMOVAL:
            //错误的移除
            notif = onVolumeBadRemoval(vol);
            break;
        default:
            notif = null;
            break;
    }
​
    if (notif != null) {
        mNotificationManager.notifyAsUser(vol.getId(), SystemMessage.NOTE_STORAGE_PUBLIC,
                                          notif, UserHandle.of(vol.getMountUserId()));
    } else {
        mNotificationManager.cancelAsUser(vol.getId(), SystemMessage.NOTE_STORAGE_PUBLIC,
                                          UserHandle.of(vol.getMountUserId()));
    }
}

处理完成之后用notificationManager做通知,后面会有对应的方法做通知的生成,这个类里面也会有很多生成PendingIntent的方法,主要是为了配合生成notificaiton的点击跳转的intent,方法名大致如下:buildInitPendingIntent(),buildUnmountPendingIntent(),buildBrowsePendingIntent(),buildVolumeSettingsPendingIntent(),buildSnoozeIntent(),buildForgetPendingIntent(),buildWizardMigratePendingIntent(),buildWizardMovePendingIntent(),buildWizardReadyPendingIntent(),这些方法名字根据英文翻译大概能确认要生成的intent的内容。

2.6PowerUI电量界面

PowerUI.java是处理关于电量部分得相关内容,都会调用start启用这个服务,其中start的代码如下:

public void start() {
    mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
    mScreenOffTime = mPowerManager.isScreenOn() ? -1 : SystemClock.elapsedRealtime();
    mWarnings = Dependency.get(WarningsUI.class);//通过这个更新UI的相关内容
    mEnhancedEstimates = Dependency.get(EnhancedEstimates.class);
    mLastConfiguration.setTo(mContext.getResources().getConfiguration());
​
    ContentObserver obs = new ContentObserver(mHandler) {
        @Override
        public void onChange(boolean selfChange) {
            updateBatteryWarningLevels();
        }
    };
    final ContentResolver resolver = mContext.getContentResolver();
    resolver.registerContentObserver(Settings.Global.getUriFor(
        Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL),
                                     false, obs, UserHandle.USER_ALL);
    updateBatteryWarningLevels();
    mReceiver.init();//调用receiver初始化接收这些状态
​
    // Check to see if we need to let the user know that the phone previously shut down due
    // to the temperature being too high.
    showWarnonThermalShutdown();
​
    // Register an observer to configure mEnableSkinTemperatureWarning and perform the
    // registration of skin thermal event listener upon Settings change.
    resolver.registerContentObserver(
        Settings.Global.getUriFor(Settings.Global.SHOW_TEMPERATURE_WARNING),
        false ,
        new ContentObserver(mHandler) {
            @Override
            public void onChange(boolean selfChange) {
                doSkinThermalEventListenerRegistration();
            }
        });
    // Register an observer to configure mEnableUsbTemperatureAlarm and perform the
    // registration of usb thermal event listener upon Settings change.
    resolver.registerContentObserver(
        Settings.Global.getUriFor(Settings.Global.SHOW_USB_TEMPERATURE_ALARM),
        false ,
        new ContentObserver(mHandler) {
            @Override
            public void onChange(boolean selfChange) {
                doUsbThermalEventListenerRegistration();
            }
        });
    initThermalEventListeners();
    mCommandQueue.addCallback(this);
}

初始化时调用showWarnOnThermalShutdown这个方法检查上次关机是不是因为温度太高,注册手机温度过高的回调,doSkinThermalEventListenerRegistration方法做手机温度过高的处理,调用doUsbThermalEventListenerRegistration这个方法处理USB接口温度过高的处理,其中receiver接受做的是电量的逻辑处理,如果需要用到处理UI的相关内容,是通过mWarnings做的界面更新内容,mWarnings是一个抽象接口,

public interface WarningsUI {
​
    
    void update(int batteryLevel, int bucket, long screenOffTime);
​
    void dismissLowBatteryWarning();
​
    void showLowBatteryWarning(boolean playSound);
​
    void dismissInvalidChargerWarning();
​
    void showInvalidChargerWarning();
​
    void updateLowBatteryWarning();
​
    boolean isInvalidChargerWarningShowing();
​
    void dismissHighTemperatureWarning();
​
    void showHighTemperatureWarning();
​
    
    void showUsbHighTemperatureAlarm();
​
    void showThermalShutdownWarning();
​
    void dump(PrintWriter pw);
​
    void userSwitched();
​
    
    void updateSnapshot(BatteryStateSnapshot snapshot);
}

他的实现类是PowerNotificationWarnings.java,这个里面做实际的电量界面更新的内容,其中的方法名字比较好理解:

showThermalShutdownWarning()
showUsbHighTemperatureAlarm()
showUsbHighTemperatureAlarmInternal()
updateLowBatteryWarning()
dismissLowBatteryWarning()
hasBatterySettings()
dismissInvalidChargerWarning()
showInvalidChargerWarning()
dismissAutoSaverSuggestion()
//还有需要比较好理解的方法名,能够直译过来方便理解,仅列举这么多

这个PowerNotificationWarnings的初始化是在PowerUI里面初始化的,通过Dependency拿到的一个单例对象。

2.7RingtonePlayer铃声播放器

RingtonePlayer是处理铃声播放器的类,start方法如下:

@Override
public void start() {
    mAsyncPlayer.setUsesWakeLock(mContext);
​
    mAudioService = IAudioService.Stub.asInterface(
        ServiceManager.getService(Context.AUDIO_SERVICE));
    try {
        mAudioService.setRingtonePlayer(mCallback);
    } catch (RemoteException e) {
        Log.e(TAG, "Problem registering RingtonePlayer: " + e);
    }
}

通过AIDL获取完AudioService之后设置了一个回调,mCallback处理铃声的相关逻辑:

private IRingtonePlayer mCallback = new IRingtonePlayer.Stub() {
    @Override
    public void play(IBinder token, Uri uri, AudioAttributes aa, float volume, boolean looping)
        throws RemoteException {
        playWithVolumeShaping(token, uri, aa, volume, looping, null);
    }
    @Override
    public void playWithVolumeShaping(IBinder token, Uri uri, AudioAttributes aa, float volume,
                                      boolean looping, @Nullable VolumeShaper.Configuration volumeShaperConfig)
        throws RemoteException {
        if (LOGD) {
            Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid="
                  + Binder.getCallingUid() + ")");
        }
        Client client;
        synchronized (mClients) {
            client = mClients.get(token);
            if (client == null) {
                final UserHandle user = Binder.getCallingUserHandle();
                client = new Client(token, uri, user, aa, volumeShaperConfig);
                token.linkToDeath(client, 0);
                mClients.put(token, client);
            }
        }
        client.mRingtone.setLooping(looping);
        client.mRingtone.setVolume(volume);
        client.mRingtone.play();
    }
​
    @Override
    public void stop(IBinder token) {
        if (LOGD) Log.d(TAG, "stop(token=" + token + ")");
        Client client;
        synchronized (mClients) {
            client = mClients.remove(token);
        }
        if (client != null) {
            client.mToken.unlinkToDeath(client, 0);
            client.mRingtone.stop();
        }
    }
​
    @Override
    public boolean isPlaying(IBinder token) {
        if (LOGD) Log.d(TAG, "isPlaying(token=" + token + ")");
        Client client;
        synchronized (mClients) {
            client = mClients.get(token);
        }
        if (client != null) {
            return client.mRingtone.isPlaying();
        } else {
            return false;
        }
    }
​
    @Override
    public void setPlaybackProperties(IBinder token, float volume, boolean looping) {
        Client client;
        synchronized (mClients) {
            client = mClients.get(token);
        }
        if (client != null) {
            client.mRingtone.setVolume(volume);
            client.mRingtone.setLooping(looping);
        }
        // else no client for token when setting playback properties but will be set at play()
    }
​
    @Override
    public void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa) {
        if (LOGD) Log.d(TAG, "playAsync(uri=" + uri + ", user=" + user + ")");
        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
            throw new SecurityException("Async playback only available from system UID.");
        }
        if (UserHandle.ALL.equals(user)) {
            user = UserHandle.SYSTEM;
        }
        mAsyncPlayer.play(getContextForUser(user), uri, looping, aa);
    }
​
    @Override
    public void stopAsync() {
        if (LOGD) Log.d(TAG, "stopAsync()");
        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
            throw new SecurityException("Async playback only available from system UID.");
        }
        mAsyncPlayer.stop();
    }
​
    @Override
    public String getTitle(Uri uri) {
        final UserHandle user = Binder.getCallingUserHandle();
        return Ringtone.getTitle(getContextForUser(user), uri,
                                 false , false );
    }
​
    @Override
    public ParcelFileDescriptor openRingtone(Uri uri) {
        final UserHandle user = Binder.getCallingUserHandle();
        final ContentResolver resolver = getContextForUser(user).getContentResolver();
​
        // only open the requested Uri if it's a well-known ringtone or
        // other sound from the platform media store, otherwise this opens
        // up arbitrary access to any file on external storage.
        if (uri.toString().startsWith(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString())) {
            try (Cursor c = resolver.query(uri, new String[] {
                MediaStore.Audio.AudioColumns.IS_RINGTONE,
                MediaStore.Audio.AudioColumns.IS_ALARM,
                MediaStore.Audio.AudioColumns.IS_NOTIFICATION
            }, null, null, null)) {
                if (c.moveToFirst()) {
                    if (c.getInt(0) != 0 || c.getInt(1) != 0 || c.getInt(2) != 0) {
                        try {
                            return resolver.openFileDescriptor(uri, "r");
                        } catch (IOException e) {
                            throw new SecurityException(e);
                        }
                    }
                }
            }
        }
        throw new SecurityException("Uri is not ringtone, alarm, or notification: " + uri);
    }
}

如上所示,这个方法名命名的比较容易理解,当调用play时,从mClients中取到binder对应的client,mClients是一个HashMap,用于保存和客户端的连接的对象,Client里面通过mRingtone做声音的处理,Ringtone就是快速设置音量的类,里面的方法名如下:

setAudioAttributes()
setLooping()
setVolume()
setUri(Uri uri)
play()
stop()

但是这个类上层是无法操作的,试过之后,这个类只能在系统里面操作。

2.8KeyboardUI键盘界面

KeyboardUI是在androidTV版配置下才存在的服务,主要是为了处理蓝牙键盘,这个蓝牙键盘在手机上是不可以使用的,里面通过蓝牙连接外设键盘,通过广播的形式处理锁屏,关机的对话框,SystemUIDialog是一个alertDialog,状态改变时会设置不同的window的属性,内部的receiver代码如下:

public static void registerDismissListener(Dialog dialog) {
    DismissReceiver dismissReceiver = new DismissReceiver(dialog);
    dialog.setonDismissListener(d -> dismissReceiver.unregister());
    dismissReceiver.register();
}
​
private static class DismissReceiver extends BroadcastReceiver {
    private static final IntentFilter INTENT_FILTER = new IntentFilter();
    static {
        INTENT_FILTER.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
        INTENT_FILTER.addAction(Intent.ACTION_SCREEN_OFF);
    }
​
    private final Dialog mDialog;
    private boolean mRegistered;
    private final BroadcastDispatcher mBroadcastDispatcher;
​
    DismissReceiver(Dialog dialog) {
        mDialog = dialog;
        mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
    }
​
    void register() {
        mBroadcastDispatcher.registerReceiver(this, INTENT_FILTER, null, UserHandle.CURRENT);
        mRegistered = true;
    }
​
    void unregister() {
        if (mRegistered) {
            mBroadcastDispatcher.unregisterReceiver(this);
            mRegistered = false;
        }
    }
​
    @Override
    public void onReceive(Context context, Intent intent) {
        mDialog.dismiss();
    }
}

这就是一个简单的广播接收器,收到广播的时候dialog取消,BluetoothDialog继承SystemUIdialog,只是简单的继承,并没有做什么处理,

public class BluetoothDialog extends SystemUIDialog {
​
    public BluetoothDialog(Context context) {
        super(context);
​
        getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
        setShowForAllUsers(true);
    }
}

KeyboardUI.java文件里面持有这个BluetoothDialog的对象,用于监听蓝牙的响应做出相应的弹框处理,KeyBoardUI.java和之前的类似,start方法如下:

public void start() {
    mContext = super.mContext;
    HandlerThread thread = new HandlerThread("Keyboard", Process.THREAD_PRIORITY_BACKGROUND);
    thread.start();
    mHandler = new KeyboardHandler(thread.getLooper());//创建
    mHandler.sendEmptyMessage(MSG_INIT);//发送初始化消息
}

这里面创建了个mHandler,是处理键盘的回调的,实例化的代码如下:

private final class KeyboardHandler extends Handler {
    public KeyboardHandler(Looper looper) {
        super(looper, null, true );
    }
​
    @Override
    public void handleMessage(Message msg) {
        switch(msg.what) {
                //初始化
            case MSG_INIT: {
                init();
                break;
            }
                //开机启动回调
            case MSG_ON_BOOT_COMPLETED: {
                onBootCompletedInternal();
                break;
            }
                //状态改变回调
            case MSG_PROCESS_KEYBOARD_STATE: {
                processKeyboardState();
                break;
            }
                //蓝牙可用不可用的回调
            case MSG_ENABLE_BLUETOOTH: {
                boolean enable = msg.arg1 == 1;
                if (enable) {
                    mLocalBluetoothAdapter.enable();
                } else {
                    mState = STATE_USER_CANCELLED;
                }
                break;
            }
                //ble扫描
            case MSG_BLE_ABORT_SCAN: {
                int scanAttempt = msg.arg1;
                bleAbortScanInternal(scanAttempt);
                break;
            }
                //蓝牙状态改变
            case MSG_ON_BLUETOOTH_STATE_CHANGED: {
                int bluetoothState = msg.arg1;
                onBluetoothStateChangedInternal(bluetoothState);
                break;
            }
                //蓝牙设备绑定状态改变
            case MSG_ON_DEVICE_BOND_STATE_CHANGED: {
                CachedBluetoothDevice d = (CachedBluetoothDevice)msg.obj;
                int bondState = msg.arg1;
                onDeviceBondStateChangedInternal(d, bondState);
                break;
            }
                //添加蓝牙设备
            case MSG_ON_BLUETOOTH_DEVICE_ADDED: {
                BluetoothDevice d = (BluetoothDevice)msg.obj;
                CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(d);
                onDeviceAddedInternal(cachedDevice);
                break;
​
            }
                //ble扫描失败
            case MSG_ON_BLE_SCAN_FAILED: {
                onBleScanFailedInternal();
                break;
            }
                //显示错误
            case MSG_SHOW_ERROR: {
                Pair p = (Pair) msg.obj;
                onShowErrorInternal(p.first, p.second, msg.arg1);
            }
        }
    }
}

mHandler创建完成之后,发送了一个初始化的message,走进init方法,init代码如下:

// Shoud only be called on the handler thread
private void init() {
    Context context = mContext;
    mKeyboardName =
        context.getString(com.android.internal.R.string.config_packagedKeyboardName);
    if (TextUtils.isEmpty(mKeyboardName)) {
        if (DEBUG) {
            Slog.d(TAG, "No packaged keyboard name given.");
        }
        return;
    }
​
    LocalBluetoothManager bluetoothManager = Dependency.get(LocalBluetoothManager.class);
    if (bluetoothManager == null)  {
        if (DEBUG) {
            Slog.e(TAG, "Failed to retrieve LocalBluetoothManager instance");
        }
        return;
    }
    mEnabled = true;
    mCachedDeviceManager = bluetoothManager.getCachedDeviceManager();
    mLocalBluetoothAdapter = bluetoothManager.getBluetoothAdapter();
    mProfileManager = bluetoothManager.getProfileManager();
    bluetoothManager.getEventManager().registerCallback(new BluetoothCallbackHandler());
    BluetoothUtils.setErrorListener(new BluetoothErrorListener());
​
    InputManager im = context.getSystemService(InputManager.class);
    im.registeronTabletModeChangedListener(this, mHandler);
    mInTabletMode = im.isInTabletMode();
​
    processKeyboardState();
    mUIHandler = new KeyboardUIHandler();//又创建了个更新键盘UI的handler
}

init方法主要拿缓冲设备,蓝牙,输入信息,然后又创建了个mUIHandler用于更新UI相关内容,mUiHandler的代码如下:

private final class KeyboardUIHandler extends Handler {
    public KeyboardUIHandler() {
        super(Looper.getMainLooper(), null, true );
    }
    @Override
    public void handleMessage(Message msg) {
        switch(msg.what) {
            case MSG_SHOW_BLUETOOTH_DIALOG: {
                if (mDialog != null) {
                    // Don't show another dialog if one is already present
                    break;
                }
                DialogInterface.onClickListener clickListener =
                    new BluetoothDialogClickListener();
                DialogInterface.onDismissListener dismissListener =
                    new BluetoothDialogDismissListener();
                mDialog = new BluetoothDialog(mContext);//dialog是之前的蓝牙dialog
                mDialog.setTitle(R.string.enable_bluetooth_title);
                mDialog.setMessage(R.string.enable_bluetooth_message);
                mDialog.setPositiveButton(
                    R.string.enable_bluetooth_/confirm/iation_ok, clickListener);
                mDialog.setNegativeButton(android.R.string.cancel, clickListener);
                mDialog.setonDismissListener(dismissListener);
                mDialog.show();
                break;
            }
            case MSG_DISMISS_BLUETOOTH_DIALOG: {
                if (mDialog != null) {
                    mDialog.dismiss();
                }
                break;
            }
        }
    }
}

这个比较简单,就是做弹框,取消弹框的处理,整体来看这个KeyboardUI里面有很多的回调,大概列举下名字如下:

processKeyboardState()
onBootCompletedInternal()
showBluetoothDialog()
getPairedKeyboard()
getDiscoveredKeyboard()
getCachedBluetoothDevice()
startScanning()
stopScanning()
bleAbortScanInternal()
onDeviceAddedInternal()
onBluetoothStateChangedInternal()
onBleScanFailedInternal()
onShowErrorInternal()

整体来看这些名字全部都和蓝牙有关系。

2.9PipUI图片窗口中的图片

PipUI是android系统处理画中画的一个SystemUI,构造方法中传入commonQueue的对象和mPipManager,mPipManager用于处理画中画的操作,PipUI重写的start方法如下:

@Override
public void start() {
    PackageManager pm = mContext.getPackageManager();
    boolean supportsPip = pm.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
    if (!supportsPip) {
        return;
    }
​
    // Ensure that we are the primary user's SystemUI.
    final int processUser = UserManager.get(mContext).getUserHandle();
    if (processUser != UserHandle.USER_SYSTEM) {
        throw new IllegalStateException("Non-primary Pip component not currently supported.");
    }
​
    mCommandQueue.addCallback(this);
}

拿到packageManager判断是否是主用户的SystemUI,之后把当前对象添加到mCommandQueue的回调中,这个类回调只做了一个处理,

@Override
public void showPictureInPictureMenu() {
    mPipManager.showPictureInPictureMenu();
}
​
public void expandPip() {
    mPipManager.expandPip();
}

显示画中画的菜单,下面的收起画中画的操作,都是通过pipManager做的操作的。

2.10ShortcutKeyDispatcher快捷分发器

ShortcutKeyDispatcher这个类主要是提供快捷功能的,构造方法如下:

@Inject
public ShortcutKeyDispatcher(Context context, Divider divider, Recents recents) {
    super(context);
    mDivider = divider;
    mRecents = recents;
}

传入分屏对象,最近使用的app进程对象,在start方法中注册内容:

@Override
public void start() {
    registerShortcutKey(SC_DOCK_LEFT);
    registerShortcutKey(SC_DOCK_RIGHT);
}

注册方法如下:

public void registerShortcutKey(long shortcutCode) {
    try {
        mWindowManagerService.registerShortcutKey(shortcutCode, mShortcutKeyServiceProxy);
    } catch (RemoteException e) {
        // Do nothing
    }
}

通过wms注册快捷进入的功能,当点击的时候触发下面的方法:

@Override
public void onShortcutKeyPressed(long shortcutCode) {
    int orientation = mContext.getResources().getConfiguration().orientation;
    if ((shortcutCode == SC_DOCK_LEFT || shortcutCode == SC_DOCK_RIGHT)
        && orientation == Configuration.ORIENTATION_LANDSCAPE) {
        handleDockKey(shortcutCode);
    }
}

继续调用handleDockKey方法:

private void handleDockKey(long shortcutCode) {
    if (mDivider == null || !mDivider.isDividerVisible()) {
        // Split the screen
        mRecents.splitPrimaryTask((shortcutCode == SC_DOCK_LEFT)
                                  ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
                                  : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, null, -1);
    } else {
        // If there is already a docked window, we respond by resizing the docking pane.
        DividerView dividerView = mDivider.getView();
        DividerSnapAlgorithm snapAlgorithm = dividerView.getSnapAlgorithm();
        int dividerPosition = dividerView.getCurrentPosition();
        DividerSnapAlgorithm.SnapTarget currentTarget =
            snapAlgorithm.calculateNonDismissingSnapTarget(dividerPosition);
        DividerSnapAlgorithm.SnapTarget target = (shortcutCode == SC_DOCK_LEFT)
            ? snapAlgorithm.getPreviousTarget(currentTarget)
            : snapAlgorithm.getNextTarget(currentTarget);
        dividerView.startDragging(true , false );
        dividerView.stopDragging(target.position, 0f, false ,
                                 true );
    }
}

判断分屏状态,如果当前分屏是空,或者分屏不可见的情况下,做分屏处理,其他的情况重新测量这个view做处理。

2.11VendorServices供应商服务

这个里面是空实现,是为了给供应商留空白的位置,暂时什么都没有。

2.12SliceBroadcastRelayHandler允许打开设置App

SliceBroadcastRelayHandler.java,这个类是通过广播实现的,注释里面的意思是允许设置注册某些广播来启动设置的app,start方法如下:

@Override
public void start() {
    if (DEBUG) Log.d(TAG, "Start");
    IntentFilter filter = new IntentFilter(SliceBroadcastRelay.ACTION_REGISTER);
    filter.addAction(SliceBroadcastRelay.ACTION_UNREGISTER);
    mBroadcastDispatcher.registerReceiver(mReceiver, filter);
}

这里面同光广播分发器注册了一个receiver,其中receiver的handleIntent()代码如下:

// This does not use BroadcastDispatcher as the filter may have schemas or mime types.
@VisibleForTesting
void handleIntent(Intent intent) {
    if (SliceBroadcastRelay.ACTION_REGISTER.equals(intent.getAction())) {
        Uri uri = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_URI);
        ComponentName receiverClass =
            intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_RECEIVER);
        IntentFilter filter = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_FILTER);
        if (DEBUG) Log.d(TAG, "Register " + uri + " " + receiverClass + " " + filter);
        getOrCreateRelay(uri).register(mContext, receiverClass, filter);
    } else if (SliceBroadcastRelay.ACTION_UNREGISTER.equals(intent.getAction())) {
        Uri uri = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_URI);
        if (DEBUG) Log.d(TAG, "Unregister " + uri);
        BroadcastRelay relay = getAndRemoveRelay(uri);
        if (relay != null) {
            relay.unregister(mContext);
        }
    }
}

里面做了两个处理,第一个是注册,第二个是取消注册,通过uri从mRelays里面拿到一个BroadcastRelay类型的对象,做注册和取消注册注册的操作,BroadcastRelay也是一个广播其中的onReceive的代码如下:

@Override
public void onReceive(Context context, Intent intent) {
    intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
    for (ComponentName receiver : mReceivers) {
        intent.setComponent(receiver);
        intent.putExtra(SliceBroadcastRelay.EXTRA_URI, mUri.toString());
        if (DEBUG) Log.d(TAG, "Forwarding " + receiver + " " + intent + " " + mUserId);
        context.sendBroadcastAsUser(intent, mUserId);
    }
}

这里面做的操作比较简单,就是把这个广播全部都发送出去,多添加了一个EXTRA_URI的标记。

2.13SizeCompatModeActivityController允许重新启动activity的控制器

SizeCompatModeActivityController这个类主要是做重新启动activity的作用,构造方法中的处理如下:

@VisibleForTesting
@Inject
SizeCompatModeActivityController(Context context, ActivityManagerWrapper am,
                                 CommandQueue commandQueue) {
    super(context);
    mCommandQueue = commandQueue;
    am.registerTaskStackListener(new TaskStackChangeListener() {
        @Override
        public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) {
            // Note the callback already runs on main thread.
            updateRestartButton(displayId, activityToken);
        }
    });
}

传入commandQueue,am中注册重新启动activity的按钮,任务栈发生改变的监听,updateRestartButton会更新一个Button或者创建一个button,这个button的onClick事件如下:

@Override
public void onClick(View v) {
    try {
        ActivityTaskManager.getService().restartActivityProcessIfVisible(
            mLastActivityToken);
    } catch (RemoteException e) {
        Log.w(TAG, "Unable to restart activity", e);
    }
}

拿到taskManager服务重启activity,入参是mLastActivityToken,这个参数就是上面需要变动的activityToken,SizeCompatModeActivityController的start方法如下:

@Override
public void start() {
    mCommandQueue.addCallback(this);
}

跟之前的操作类似,添加自身作为回调,重写的方法有两个:

@Override
public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
                               boolean showImeSwitcher) {
    RestartActivityButton button = mActiveButtons.get(displayId);
    if (button == null) {
        return;
    }
    boolean imeShown = (vis & InputMethodService.IME_VISIBLE) != 0;
    int newVisibility = imeShown ? View.GONE : View.VISIBLE;
    // Hide the button when input method is showing.
    if (button.getVisibility() != newVisibility) {
        button.setVisibility(newVisibility);
    }
}
​
@Override
public void onDisplayRemoved(int displayId) {
    mDisplayContextCache.remove(displayId);
    removeRestartButton(displayId);
}

这两个方法都是在控制button显示或者不显示。

2.14InstantAppNotifier即时应用程序通知

InstantAppNotifier是即时应用程序的通知,即时应用程序的概念和微信小程序类似, “快应用”标准是九家手机厂商基于硬件平台共同推出的新型应用生态,快应用使用前端技术栈开发,原生渲染,同时具备H5页面和原生应用的双重优点。用户无需下载安装,即点即用,享受原生应用的性能体验。

  1. 快应用是基于手机硬件平台的新型应用形态,标准是由主流手机厂商组成的快应用联盟联合制定。

  2. 快应用标准的诞生将在研发接口、能力接入、开发者服务等层面建设标准平台,以平台化的生态模式对个人开发者和企业开发者全品类开放。

  3. 快应用具备传统APP完整的应用体验,无需安装、即点即用。

简单的说就是允许用户免安装直接打开网页做对应的用户操作,打开android应用商店搜索“快应用”能够直接搜索到, 主要是针对小型app,大型app在这个快应用中心并没有,本身针对的体量就比较小,快应用开发参考文档地址:

[快应用开发参考文档]  https://www.w3cschool.cn/quickapp/quickapp-6ams2o7m.html no

这个类的start方法中对键盘,分屏,通知做了初始化,代码如下:

@Override
public void start() {
    mKeyguardStateController = Dependency.get(KeyguardStateController.class);
​
    // listen for user / profile change.
    try {
        ActivityManager.getService().registerUserSwitchObserver(mUserSwitchListener, TAG);
    } catch (RemoteException e) {
        // Ignore
    }
​
    mCommandQueue.addCallback(this);
    mKeyguardStateController.addCallback(this);
​
    mDivider.registerInSplitScreenListener(
        exists -> {
            mDockedStackExists = exists;
            updateForegroundInstantApps();
        });
​
    // Clear out all old notifications on startup (only present in the case where sysui dies)
    NotificationManager noMan = mContext.getSystemService(NotificationManager.class);
    for (StatusBarNotification notification : noMan.getActiveNotifications()) {
        if (notification.getId() == SystemMessage.NOTE_INSTANT_APPS) {
            noMan.cancel(notification.getTag(), notification.getId());
        }
    }
}

后面调用通知的方法名是:

updateForegroundInstantApps//(通知前台快应用的app)
checkAndPostForPrimaryScreen//(检查通知主屏幕)
checkAndPostForStack//(检查并通知栈)

2.15ToastUI弹窗提示

ToastUI是系统的弹窗提示,继承自SystemUI实现CommandQueue.Callbacks接口,调用start方法后,把当前对象的回调传给mCommandQueue,代码如下:

@VisibleForTesting
ToastUI(Context context, CommandQueue commandQueue, INotificationManager notificationManager,
        @Nullable IAccessibilityManager accessibilityManager) {
    super(context);
    mCommandQueue = commandQueue;
    mNotificationManager = notificationManager;
    mAccessibilityManager = accessibilityManager;
    Resources resources = mContext.getResources();
    mGravity = resources.getInteger(R.integer.config_toastDefaultGravity);
    mY = resources.getDimensionPixelSize(R.dimen.toast_y_offset);
}
​
@Override
public void start() {
    mCommandQueue.addCallback(this);
}
​
@Override
@MainThread
public void showToast(int uid, String packageName, IBinder token, CharSequence text,
                      IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) {
    if (mPresenter != null) {
        hideCurrentToast();
    }
    Context context = mContext.createContextAsUser(UserHandle.getUserHandleForUid(uid), 0);
    View view = ToastPresenter.getTextToastView(context, text);
    mCallback = callback;
    mPresenter = new ToastPresenter(context, mAccessibilityManager, mNotificationManager,
                                    packageName);
    mPresenter.show(view, token, windowToken, duration, mGravity, 0, mY, 0, 0, mCallback);
}
​
@Override
@MainThread
public void hideToast(String packageName, IBinder token) {
    if (mPresenter == null || !Objects.equals(mPresenter.getPackageName(), packageName)
        || !Objects.equals(mPresenter.getToken(), token)) {
        Log.w(TAG, "Attempt to hide non-current toast from package " + packageName);
        return;
    }
    hideCurrentToast();
}
​
@MainThread
private void hideCurrentToast() {
    mPresenter.hide(mCallback);
    mPresenter = null;
}

初始化时赋值mCommandQueue,这个队列是实现消息回传的一个类,ToastUI中的showToast和hideToast都加的有主线程的注解,这个充当Model层和Presenter层的中间层,数据源发生变化,需要调用showToast时,通过new Presenter构造prensenter后调用presenter层的show方法,也是MVP的架构,presenter的主要代码如下:

构造方法:

public ToastPresenter(Context context, IAccessibilityManager accessibilityManager,
                      INotificationManager notificationManager, String packageName) {
    mContext = context;
    mResources = context.getResources();
    mWindowManager = context.getSystemService(WindowManager.class);
    mNotificationManager = notificationManager;
    mPackageName = packageName;
​
    // We obtain AccessibilityManager manually via its constructor instead of using method
    // AccessibilityManager.getInstance() for 2 reasons:
    //   1. We want to be able to inject IAccessibilityManager in tests to verify behavior.
    //   2. getInstance() caches the instance for the process even if we pass a different
    //      context to it. This is problematic for multi-user because callers can pass a context
    //      created via Context.createContextAsUser().
    mAccessibilityManager = new AccessibilityManager(context, accessibilityManager,
                                                     context.getUserId());
​
    mParams = createLayoutParams();
}

show和hide方法:

public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity,
                 int xOffset, int yOffset, float horizontalMargin, float verticalMargin,
                 @Nullable ITransientNotificationCallback callback) {
    checkState(mView == null, "only one toast at a time is allowed, call hide() first.");
    mView = view;
    mToken = token;
​
    adjustLayoutParams(mParams, windowToken, duration, gravity, xOffset, yOffset,
                       horizontalMargin, verticalMargin);
    if (mView.getParent() != null) {
        mWindowManager.removeView(mView);
    }
    try {
        mWindowManager.addView(mView, mParams);
    } catch (WindowManager.BadTokenException e) {
        // Since the notification manager service cancels the token right after it notifies us
        // to cancel the toast there is an inherent race and we may attempt to add a window
        // after the token has been invalidated. Let us hedge against that.
        Log.w(TAG, "Error while attempting to show toast from " + mPackageName, e);
        return;
    }
    trySendAccessibilityEvent(mView, mPackageName);
    if (callback != null) {
        try {
            callback.onToastShown();
        } catch (RemoteException e) {
            Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastShow()", e);
        }
    }
}
​

public void hide(@Nullable ITransientNotificationCallback callback) {
    checkState(mView != null, "No toast to hide.");
​
    if (mView.getParent() != null) {
        mWindowManager.removeViewImmediate(mView);
    }
    try {
        mNotificationManager.finishToken(mPackageName, mToken);
    } catch (RemoteException e) {
        Log.w(TAG, "Error finishing toast window token from package " + mPackageName, e);
    }
    if (callback != null) {
        try {
            callback.onToastHidden();
        } catch (RemoteException e) {
            Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastHide()", e);
        }
    }
    mView = null;
    mToken = null;
}

presenter层直接通过windowManager的addView和removeView方法更新界面。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/462126.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号