Android 12系统源码_多屏幕(二)模拟辅助设备功能开关实现原理

前言

上一篇我们通过为Android系统开启模拟辅助设备功能开关,最终实现了将一个Activity显示到多个屏幕的效果。
模拟辅助设备功能开关
开启一块新的虚拟屏幕设备
本篇文章我们具体来分析一下当我们开启模拟辅助设备功能开关的时候,Android系统做了什么哪些操作。

一、模拟辅助设备功能开关应用位置

Android12系统中,车机系统有一个专门的开发者选项页面,其完整名称如下:

com.android.car.developeroptions/com.android.car.developeroptions.CarDevelopmentSettingsDashboardActivity

可以发现此Activity对应的包名为com.android.car.developeroptions,输入adb命令:

adb shell pm path com.android.car.developeroptions

返回的结果是:

/system_ext/priv-app/CarDeveloperOptions/CarDeveloperOptions.apk

可以发现是一个名为CarDeveloperOptions的车机系统应用,直接在aosp源码中进行搜索,搜索结果如下:
在这里插入图片描述
可以发现这个系统应用位于/packages/services/Car/packages/CarDeveloperOptions目录。

二、模拟辅助设备功能开关相关源码

2.1 系统开发者选项对应的页面声明

CarDeveloperOptions系统应用的AndroidManifest.xml文件中对开发者选项页面的声明如下。

services/Car/packages/CarDeveloperOptions/AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
          xmlns:tools="http://schemas.android.com/tools"
          coreApp="true"
          package="com.android.car.developeroptions"
          android:sharedUserId="android.uid.system">
		...代码省略...
        <activity
            android:name=".CarDevelopmentSettingsDashboardActivity"
            android:enabled="false"
            android:exported="true"
            android:icon="@drawable/ic_settings_development"
            android:label="@string/development_settings_title"
            android:taskAffinity=""
            android:theme="@style/Theme.CarDeveloperOptions">
            <intent-filter android:priority="1">
                <action android:name="android.settings.APPLICATION_DEVELOPMENT_SETTINGS"/>
                <action android:name="com.android.settings.APPLICATION_DEVELOPMENT_SETTINGS"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
            <meta-data android:name="com.android.settings.summary"
                       android:resource="@string/summary_empty"/>
            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                       android:value="com.android.car.developeroptions.CarDevelopmentSettingsDashboardFragment"/>
            <meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
                       android:value="true"/>
        </activity>
        ...代码省略...
</manifest>

2.2 系统开发者选项对应的Activity

1、CarDevelopmentSettingsDashboardActivity的系统源码非常简洁。

services/Car/packages/CarDeveloperOptions/src/com/android/car/developeroptions/CarDevelopmentSettingsDashboardActivity.java

public class CarDevelopmentSettingsDashboardActivity extends SettingsActivity {
    private static final String CAR_DEVELOPMENT_SETTINGS_FRAGMENT =
            "com.android.car.developeroptions.CarDevelopmentSettingsDashboardFragment";

    @Override
    protected boolean isValidFragment(String fragmentName) {
        return CAR_DEVELOPMENT_SETTINGS_FRAGMENT.equals(fragmentName);
    }

    @Override
    protected boolean isToolbarEnabled() {
        // Disable the default Settings toolbar in favor of a chassis toolbar.
        return false;
    }
}

此类中的源码非常简单,最关键的就是CAR_DEVELOPMENT_SETTINGS_FRAGMENT 这个字段,该字段指向了一个Fragment,该Fragment才是开发者选项页面的真正载体,

2、想要明白CarDevelopmentSettingsDashboardActivity页面的具体加载流程,我们有必要看下其父类SettingsActivity 。

packages/apps/Settings/src/com/android/settings/SettingsActivity.java

public class SettingsActivity extends SettingsBaseActivity
        implements PreferenceManager.OnPreferenceTreeClickListener,
        PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
        ButtonBarHandler, FragmentManager.OnBackStackChangedListener {
    @Override
    protected void onCreate(Bundle savedState) {
		...代码省略...
		//加载布局文件
        setContentView(R.layout.settings_main_prefs);
        ...代码省略...
        //获取页面参数
        final String initialFragmentName = getInitialFragmentName(intent);
		...代码省略...
		//加载设置模块具体页面对应的fragment
		launchSettingFragment(initialFragmentName, intent);
		...代码省略...
	}

    /**
     * 将initialFragmentName指向的fragment加载到当前Activity中
     */
    void launchSettingFragment(String initialFragmentName, Intent intent) {
        if (initialFragmentName != null) {
            setTitleFromIntent(intent);
            Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
            switchToFragment(initialFragmentName, initialArguments, true,
                    mInitialTitleResId, mInitialTitle);
        } else {
            // Show search icon as up affordance if we are displaying the main Dashboard
            mInitialTitleResId = R.string.dashboard_title;
            switchToFragment(TopLevelSettings.class.getName(), null /* args */, false,
                    mInitialTitleResId, mInitialTitle);
        }
    }

    /**
     * 将fragmentName指向的fragment加载到当前Activity中
     */
    private void switchToFragment(String fragmentName, Bundle args, boolean validate,
            int titleResId, CharSequence title) {
        Log.d(LOG_TAG, "Switching to fragment " + fragmentName);
        if (validate && !isValidFragment(fragmentName)) {
            throw new IllegalArgumentException("Invalid fragment for this activity: "
                    + fragmentName);
        }
        Fragment f = Utils.getTargetFragment(this, fragmentName, args);
        if (f == null) {
            return;
        }
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.replace(R.id.main_content, f);
        if (titleResId > 0) {
            transaction.setBreadCrumbTitle(titleResId);
        } else if (title != null) {
            transaction.setBreadCrumbTitle(title);
        }
        transaction.commitAllowingStateLoss();
        getSupportFragmentManager().executePendingTransactions();
        Log.d(LOG_TAG, "Executed frag manager pendingTransactions");
    }
}

packages/apps/Settings/res/layout/settings_main_prefs.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_height="match_parent"
              android:layout_width="match_parent">

    <com.android.settings.widget.SettingsMainSwitchBar
        android:id="@+id/switch_bar"
        android:visibility="gone"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <FrameLayout
        android:id="@+id/main_content"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

    <RelativeLayout android:id="@+id/button_bar"
                    android:layout_height="wrap_content"
                    android:layout_width="match_parent"
                    android:layout_weight="0"
                    android:visibility="gone">

        <Button android:id="@+id/back_button"
                android:layout_width="150dip"
                android:layout_height="wrap_content"
                android:layout_margin="5dip"
                android:layout_alignParentStart="true"
                android:text="@*android:string/back_button_label"/>

        <LinearLayout
                android:orientation="horizontal"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentEnd="true">

            <Button android:id="@+id/skip_button"
                    android:layout_width="150dip"
                    android:layout_height="wrap_content"
                    android:layout_margin="5dip"
                    android:text="@*android:string/skip_button_label"
                    android:visibility="gone"/>

            <Button android:id="@+id/next_button"
                    android:layout_width="150dip"
                    android:layout_height="wrap_content"
                    android:layout_margin="5dip"
                    android:text="@*android:string/next_button_label"/>

        </LinearLayout>

    </RelativeLayout>

</LinearLayout>

上面我们列出了SettingsActivity和UI加载相关的源码,可以发现SettingsActivity先是加载了一个名为settings_main_prefs的布局文件,然后将initialFragmentName指向的fragment添加到了当前页面上。结合CarDevelopmentSettingsDashboardActivity的源码我们可以知道,开发者选项页面的真正载体是CarDevelopmentSettingsDashboardFragment。

packages/services/Car/packages/CarDeveloperOptions/src/com/android/car/developeroptions/CarDevelopmentSettingsDashboardFragment.java

public class CarDevelopmentSettingsDashboardFragment extends DevelopmentSettingsDashboardFragment {

}

packages/apps/Settings/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java

public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFragment
        implements OnMainSwitchChangeListener, OemUnlockDialogHost, AdbDialogHost,
        AdbClearKeysDialogHost, LogPersistDialogHost,
        BluetoothA2dpHwOffloadRebootDialog.OnA2dpHwDialogConfirmedListener,
        AbstractBluetoothPreferenceController.Callback {

    @Override
    protected int getPreferenceScreenResId() {
        return Utils.isMonkeyRunning() ? R.xml.placeholder_prefs : R.xml.development_settings;//页面对应的布局文件
    }

    private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
            Activity activity, Lifecycle lifecycle, DevelopmentSettingsDashboardFragment fragment,
            BluetoothA2dpConfigStore bluetoothA2dpConfigStore) {
        final List<AbstractPreferenceController> controllers = new ArrayList<>();
       	...代码省略...
        controllers.add(new SecondaryDisplayPreferenceController(context));//模拟辅助设备功能组件控制器
        controllers.add(new GpuViewUpdatesPreferenceController(context));
        controllers.add(new HardwareLayersUpdatesPreferenceController(context));
        controllers.add(new DebugGpuOverdrawPreferenceController(context));
        controllers.add(new DebugNonRectClipOperationsPreferenceController(context));
        controllers.add(new ForceDarkPreferenceController(context));
        controllers.add(new EnableBlursPreferenceController(context));
        controllers.add(new ForceMSAAPreferenceController(context));
        controllers.add(new HardwareOverlaysPreferenceController(context));
        controllers.add(new SimulateColorSpacePreferenceController(context));
        controllers.add(new UsbAudioRoutingPreferenceController(context));
        controllers.add(new StrictModePreferenceController(context));
        controllers.add(new ProfileGpuRenderingPreferenceController(context));
        controllers.add(new KeepActivitiesPreferenceController(context));
        controllers.add(new BackgroundProcessLimitPreferenceController(context));
        controllers.add(new CachedAppsFreezerPreferenceController(context));
        controllers.add(new ShowFirstCrashDialogPreferenceController(context));
        controllers.add(new AppsNotRespondingPreferenceController(context));
        controllers.add(new NotificationChannelWarningsPreferenceController(context));
        controllers.add(new AllowAppsOnExternalPreferenceController(context));
        controllers.add(new ResizableActivityPreferenceController(context));
        controllers.add(new FreeformWindowsPreferenceController(context));
        controllers.add(new DesktopModePreferenceController(context));
        controllers.add(new NonResizableMultiWindowPreferenceController(context));
        controllers.add(new ShortcutManagerThrottlingPreferenceController(context));
        controllers.add(new EnableGnssRawMeasFullTrackingPreferenceController(context));
        controllers.add(new DefaultLaunchPreferenceController(context, "running_apps"));
        controllers.add(new DefaultLaunchPreferenceController(context, "demo_mode"));
        controllers.add(new DefaultLaunchPreferenceController(context, "quick_settings_tiles"));
        controllers.add(new DefaultLaunchPreferenceController(context, "feature_flags_dashboard"));
        controllers.add(new DefaultUsbConfigurationPreferenceController(context));
        controllers.add(new DefaultLaunchPreferenceController(context, "density"));
        controllers.add(new DefaultLaunchPreferenceController(context, "background_check"));
        controllers.add(new DefaultLaunchPreferenceController(context, "inactive_apps"));
        controllers.add(new AutofillLoggingLevelPreferenceController(context, lifecycle));
        controllers.add(new AutofillResetOptionsPreferenceController(context));
        controllers.add(new BluetoothCodecDialogPreferenceController(context, lifecycle,
                bluetoothA2dpConfigStore, fragment));
        controllers.add(new BluetoothSampleRateDialogPreferenceController(context, lifecycle,
                bluetoothA2dpConfigStore));
        controllers.add(new BluetoothBitPerSampleDialogPreferenceController(context, lifecycle,
                bluetoothA2dpConfigStore));
        controllers.add(new BluetoothQualityDialogPreferenceController(context, lifecycle,
                bluetoothA2dpConfigStore));
        controllers.add(new BluetoothChannelModeDialogPreferenceController(context, lifecycle,
                bluetoothA2dpConfigStore));
        controllers.add(new BluetoothHDAudioPreferenceController(context, lifecycle,
                bluetoothA2dpConfigStore, fragment));
        controllers.add(new SharedDataPreferenceController(context));
        controllers.add(new OverlaySettingsPreferenceController(context));

        return controllers;
    }
}

2.3 模拟辅助显示设备功能开关

1、由于CarDevelopmentSettingsDashboardFragment构建页面也和其他Settings模块的页面一样,大量使用了Preference这套组件来构建页面,如果对于Preference完全不了解,可以参考一下Android 12系统源码_Settings(一)认识Preference这篇文章。
由于Preference构建视图和常见的Android构建视图的方案有很大差异,要想使用Android那套UI架构来分析Settings模块的源码基本不可行,这里我们直接在aosp中搜索“模拟辅助显示设备”这几个字,搜索结果如下所示。
在这里插入图片描述
可以发现“模拟辅助显示设备”这个字符串对应的资源名称为overlay_display_devices_title。

2、继续在aosp中进行类型为.xml,名称为overlay_display_devices_title的资源的搜索,会发现development_settings.xml这个文件有引用。
aosp搜索结果

packages/apps/Settings/res/xml/development_settings.xml

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
                  xmlns:settings="http://schemas.android.com/apk/res-auto"
                  android:key="development_prefs_screen"
                  android:title="@string/development_settings_title">
	...代码省略...
    <PreferenceCategory
        android:key="debug_drawing_category"
        android:title="@string/debug_drawing_category"
        android:order="600">
		...代码省略...
		<!--模拟辅助显示设备功能开关-->
        <ListPreference
            android:key="overlay_display_devices"
            android:title="@string/overlay_display_devices_title"
            android:entries="@array/overlay_display_devices_entries"
            android:entryValues="@array/overlay_display_devices_values" />
		...代码省略...
    </PreferenceCategory>
	...代码省略...
</PreferenceScreen>

base/packages/SettingsLib/res/values-zh-rCN/arrays.xml

    <!-- 模拟辅助设备的条目标题 -->
  <string-array name="overlay_display_devices_entries">
    <item msgid="4497393944195787240">"无"</item>
    <item msgid="8461943978957133391">"480p"</item>
    <item msgid="6923083594932909205">"480p(安全)"</item>
    <item msgid="1226941831391497335">"720p"</item>
    <item msgid="7051983425968643928">"720p(安全)"</item>
    <item msgid="7765795608738980305">"1080p"</item>
    <item msgid="8084293856795803592">"1080p(安全)"</item>
    <item msgid="938784192903353277">"4K"</item>
    <item msgid="8612549335720461635">"4K(安全)"</item>
    <item msgid="7322156123728520872">"4K(画质提升)"</item>
    <item msgid="7735692090314849188">"4K(画质提升、安全)"</item>
    <item msgid="7346816300608639624">"720p,1080p(双屏)"</item>
  </string-array>

base/packages/SettingsLib/res/values/arrays.xml

    <!-- 模拟辅助设备的条目属性值 -->
    <string-array name="overlay_display_devices_values" translatable="false" >
        <item></item>
        <item>720x480/142</item>
        <item>720x480/142,secure</item>
        <item>1280x720/213</item>
        <item>1280x720/213,secure</item>
        <item>1920x1080/320</item>
        <item>1920x1080/320,secure</item>
        <item>3840x2160/320</item>
        <item>3840x2160/320,secure</item>
        <item>1920x1080/320|3840x2160/640</item>
        <item>1920x1080/320|3840x2160/640,secure</item>
        <item>1280x720/213;1920x1080/320</item>
    </string-array>

结合布局文件可知,key值为overlay_display_devices的ListPreference组件就是我们要找的模拟辅助显示设备功能开关组件,其功能开关子条目标题和属性值刚好对应了

3、进一步在aosp中进行类型为.java,名称为overlay_display_devices的资源的搜索,会发现SecondaryDisplayPreferenceController.java这个类有引用,前面承载开发者设置页面内容的DevelopmentSettingsDashboardFragment里面就有引用到这个类。
在这里插入图片描述

/packages/apps/Settings/src/com/android/settings/development/SecondaryDisplayPreferenceController.java

public class SecondaryDisplayPreferenceController extends DeveloperOptionsPreferenceController
        implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin {

    private static final String OVERLAY_DISPLAY_DEVICES_KEY = "overlay_display_devices";

    private final String[] mListValues;
    private final String[] mListSummaries;

    public SecondaryDisplayPreferenceController(Context context) {
        super(context);
        mListValues = context.getResources().getStringArray(R.array.overlay_display_devices_values);
        mListSummaries = context.getResources().getStringArray(
                R.array.overlay_display_devices_entries);
    }

    @Override
    public String getPreferenceKey() {
        return OVERLAY_DISPLAY_DEVICES_KEY;//preference组件的唯一key值
    }

    @Override
    public boolean onPreferenceChange(Preference preference, Object newValue) {
    	//用户选择了条目内容,对开关属性进行更新和数据保存
        writeSecondaryDisplayDevicesOption(newValue.toString());
        return true;
    }

    @Override
    public void updateState(Preference preference) {
    	//初始化模拟辅助设备功能更开关的属性值
        updateSecondaryDisplayDevicesOptions();
    }

    @Override
    protected void onDeveloperOptionsSwitchDisabled() {
        super.onDeveloperOptionsSwitchDisabled();
        writeSecondaryDisplayDevicesOption(null);
    }

    private void updateSecondaryDisplayDevicesOptions() {
    	//从global中获取当前模拟辅助设备功能开关的属性值
        final String value = Settings.Global.getString(mContext.getContentResolver(),
                Settings.Global.OVERLAY_DISPLAY_DEVICES);
        //获取当前选中的条目序列号
        int index = 0; // default
        for (int i = 0; i < mListValues.length; i++) {
            if (TextUtils.equals(value, mListValues[i])) {
                index = i;
                break;
            }
        }
        final ListPreference listPreference = (ListPreference) mPreference;
        //设置模拟辅助设备功能开关菜单条目列表中当前选中的条目
        listPreference.setValue(mListValues[index]);
        listPreference.setSummary(mListSummaries[index]);
    }

    private void writeSecondaryDisplayDevicesOption(String newValue) {
    	//更新模拟辅助设备功能开关的属性值到global里面
        Settings.Global.putString(mContext.getContentResolver(),
                Settings.Global.OVERLAY_DISPLAY_DEVICES, newValue);
        updateSecondaryDisplayDevicesOptions();
    }
}
public final class Settings {
        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
        @TestApi
        @Readable
        public static final String OVERLAY_DISPLAY_DEVICES = "overlay_display_devices";//模拟辅助设备功能开关属性对应的字段
}

用户对模拟辅助设备功能开关的操作最终会触发SecondaryDisplayPreferenceController的onPreferenceChange方法回调,该方法调用writeSecondaryDisplayDevicesOption将当前用户的选择是开关属性值以key值为overlay_display_devices保存到了global里面,这就意味着我们通过模拟辅助设备功能开关,最终就只是将一串字符串存储到了key为overlay_display_devices的值的global内容。

三、模拟辅助设备功能开关监听者

1、OverlayDisplayAdapter类中有对global的overlay_display_devices字段的变化做监听,这样该字段发生变化的时候可以收到回调。

frameworks/base/services/core/java/com/android/server/display/OverlayDisplayAdapter.java

final class OverlayDisplayAdapter extends DisplayAdapter {

    private final Handler mUiHandler;

    // Called with SyncRoot lock held.
    public OverlayDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
            Context context, Handler handler, Listener listener, Handler uiHandler) {
        super(syncRoot, context, handler, listener, TAG);
        mUiHandler = uiHandler;
    }
    
    @Override
    public void registerLocked() {
        super.registerLocked();

        getHandler().post(new Runnable() {
            @Override
            public void run() {
            	//注册监听overlay_display_devices字段的内容变化
                getContext().getContentResolver().registerContentObserver(
                        Settings.Global.getUriFor(Settings.Global.OVERLAY_DISPLAY_DEVICES),
                        true, new ContentObserver(getHandler()) {
                            @Override
                            public void onChange(boolean selfChange) {	
                            	//触发回调
                                updateOverlayDisplayDevices();
                            }
                        });

                updateOverlayDisplayDevices();
            }
        });
    }

    private void updateOverlayDisplayDevices() {
        synchronized (getSyncRoot()) {
        	//继续调用updateOverlayDisplayDevicesLocked方法
            updateOverlayDisplayDevicesLocked();
        }
    }

 }

2、global的overlay_display_devices字段内容发生变化的时候,会回调OverlayDisplayAdapter的updateOverlayDisplayDevices方法。
该方法上锁之后继续调用updateOverlayDisplayDevicesLocked方法。

final class OverlayDisplayAdapter extends DisplayAdapter {

    private final ArrayList<OverlayDisplayHandle> mOverlays =
            new ArrayList<OverlayDisplayHandle>();
    private String mCurrentOverlaySetting = "";//当前的模拟辅助设备属性值

    private void updateOverlayDisplayDevicesLocked() {
        String value = Settings.Global.getString(getContext().getContentResolver(),
                Settings.Global.OVERLAY_DISPLAY_DEVICES);
        if (value == null) {
            value = "";
        }

        if (value.equals(mCurrentOverlaySetting)) {
            return;
        }
        mCurrentOverlaySetting = value;

        if (!mOverlays.isEmpty()) {
            Slog.i(TAG, "Dismissing all overlay display devices.");
            for (OverlayDisplayHandle overlay : mOverlays) {
                overlay.dismissLocked();
            }
            mOverlays.clear();
        }

        int count = 0;
        for (String part : value.split(DISPLAY_SPLITTER)) {
            Matcher displayMatcher = DISPLAY_PATTERN.matcher(part);
            if (displayMatcher.matches()) {
                if (count >= 4) {
                    Slog.w(TAG, "Too many overlay display devices specified: " + value);
                    break;
                }
                String modeString = displayMatcher.group(1);
                String flagString = displayMatcher.group(2);
                ArrayList<OverlayMode> modes = new ArrayList<>();
                for (String mode : modeString.split(MODE_SPLITTER)) {
                    Matcher modeMatcher = MODE_PATTERN.matcher(mode);
                    if (modeMatcher.matches()) {
                        try {
                            int width = Integer.parseInt(modeMatcher.group(1), 10);
                            int height = Integer.parseInt(modeMatcher.group(2), 10);
                            int densityDpi = Integer.parseInt(modeMatcher.group(3), 10);
                            if (width >= MIN_WIDTH && width <= MAX_WIDTH
                                    && height >= MIN_HEIGHT && height <= MAX_HEIGHT
                                    && densityDpi >= DisplayMetrics.DENSITY_LOW
                                    && densityDpi <= DisplayMetrics.DENSITY_XXXHIGH) {
                                modes.add(new OverlayMode(width, height, densityDpi));
                                continue;
                            } else {
                                Slog.w(TAG, "Ignoring out-of-range overlay display mode: " + mode);
                            }
                        } catch (NumberFormatException ex) {
                        }
                    } else if (mode.isEmpty()) {
                        continue;
                    }
                }
                if (!modes.isEmpty()) {
                    int number = ++count;
                    String name = getContext().getResources().getString(
                            com.android.internal.R.string.display_manager_overlay_display_name,
                            number);
                    int gravity = chooseOverlayGravity(number);
                    OverlayFlags flags = OverlayFlags.parseFlags(flagString);

                    Slog.i(TAG, "Showing overlay display device #" + number
                            + ": name=" + name + ", modes=" + Arrays.toString(modes.toArray())
                            + ", flags=" + flags);

                    mOverlays.add(new OverlayDisplayHandle(name, modes, gravity, flags, number));
                    continue;
                }
            }
            Slog.w(TAG, "Malformed overlay display devices setting: " + value);
        }
    }

 }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/871002.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于web框架的协同过滤的美食推荐系统【数据爬虫、管理系统、数据可更新、样式可调整】

文章目录 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主项目介绍研究背景研究的目的与意义协同过滤算法基于用户的协同过滤算法定义基于物品的协同过滤算法的定义 数据库设计db_food&#xff08;美食信息表&#xff09;db_collect&#xff08;美食…

【Kubernetes】k8s集群图形化管理工具之rancher

目录 一.Rancher概述 1.Rancher简介 2.Rancher与k8s的关系及区别 3.Rancher具有的优势 二.Rancher的安装部署 1.实验准备 2.安装 rancher 3.rancher的浏览器使用 一.Rancher概述 1.Rancher简介 Rancher 是一个开源的企业级多集群 Kubernetes 管理平台&#xff0c;实…

【Linux网络】select函数

欢迎来到 破晓的历程的 博客 ⛺️不负时光&#xff0c;不负己✈️ 文章目录 select函数介绍select函数参数介绍select函数返回值select的工作流程TCP服务器【多路复用版】 select函数介绍 在Linux网络编程中&#xff0c;select 函数是一种非常有用的IO多路复用技术&#xff0…

基于springboot的车辆违章信息管理系统

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

鸿萌数据恢复服务:SQL Server 中的“PFS 可用空间信息不正确”错误

天津鸿萌科贸发展有限公司从事数据安全服务二十余年&#xff0c;致力于为各领域客户提供专业的数据恢复、数据备份、网络及终端数据安全等解决方案与服务。 同时&#xff0c;鸿萌是国际主流数据恢复软件(Stellar、UFS、R-Studio、ReclaiMe Pro 等)的授权代理商&#xff0c;为专…

RK3568笔记五十六:yolov8_obb旋转框训练部署

若该文为原创文章,转载请注明原文出处。 本文基于rknn_model_zoo和山水无移大佬的博客和代码训练模型并部署到正点原子的ATK-DLRK3568板子测试。 https://github.com/ultralytics/ultralytics 一、训练 1、环境搭建 使用的是AUTODL环境,yolov8-obb数据集不大,也可以使用c…

歌曲爬虫下载

本次编写一个程序要爬取歌曲音乐榜https://www.onenzb.com/ 里面歌曲。有帮到铁子的可以收藏和关注起来&#xff01;&#xff01;&#xff01;废话不多说直接上代码。 1 必要的包 import requests from lxml import html,etree from bs4 import BeautifulSoup import re impo…

代码块分类

局部代码块 public class Test {public static void main(String[] args) {{int a 10;}// 执行到此处时候,变量a已经从内存中消失了。 // System.out.println(a);} } 构造代码块 public class Test {private String name;private int age;{// 构造代码块System.out.…

c#实现数据导出为PDF的方式

PdfSharp vs iTextSharp: C#中PDF导出功能比较 PdfSharp 优点 轻量级&#xff1a;适合简单的PDF生成任务易于学习&#xff1a;API相对简单&#xff0c;学习曲线较缓开源&#xff1a;提供开源版本&#xff0c;可自由使用和修改纯C#实现&#xff1a;不依赖外部库或COM组件支持…

C:每日一练:单身狗(2.0版本)

前言&#xff1a; 今天在刷题的时候突然看到一道题&#xff0c;疑似一位故题。仔细一看&#xff0c;欸&#xff01;这不是就是单身狗的升级版吗&#xff1f;我想那必须再安排一篇&#xff0c;不过由于本篇文章与上一篇单身狗文章所涉及的知识点基本相同&#xff0c;所以还请大…

设计模式在芯片验证中的应用——状态

一、状态模式 状态模式是一种行为设计模式&#xff0c; 让你能在一个对象的内部状态变化时改变其行为&#xff0c; 使其看上去就像改变了自身所属的类一样。 在RTL中可能存在复杂的有限状态机FSM&#xff0c;在任何一个特定状态中&#xff0c; RTL的行为都不相同&#xff0c;…

【前缀和算法】--- 一维和二维前缀和模板

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; 算法Journey 本文开始,博主开始讲解有关前缀和的算法&#xff0c;本篇博客我们先来了解一下有关前缀和的两个模板。 &#x1f3e0; 一维前缀和模板 &…

【网络】局域网LAN、广域网WAN、TCP/IP协议、封装和分用

文章目录 局域网 LAN广域网 WAN网络中的重要概念IP 地址端口号 认识协议协议分层是什么OSI 七层网络模型TCP/IP 五层网络模型&#xff08;或四层&#xff09;物理层传输层网络层数据链表层应用层网络设备所在分层 封装和分用[站在发送方视角]&#xff08;封装&#xff09;[站在…

邮票孔拼版制作方法

邮票孔拼版制作方法 拼版后的局部图:(中间用连接桥的方式&#xff0c;此方式能最少程度上减少残留) 2&#xff09;拼版后的效果图 3&#xff09;邮票孔拼版规则: 拼板与板间距1.2MM或者1.6MM 等邮票孔&#xff1a;8个0.55MM的孔,孔间距0.2MM加两排&#xff0c;邮票孔伸到…

linux服务 学习

服务&#xff08;Service&#xff09; 在Linux操作系统中&#xff0c;服务&#xff08;Service&#xff09;是一个基本概念&#xff0c;它通常指的是运行在后台的、持续提供特定功能或资源给系统内部组件或者网络上的客户端程序。 这些服务是系统正常运行和提供各种功能的关键…

【三维重建汇总】NeRF和GS重建中,如何排除干扰物?(提升质量)

汇总最近NeRF与GS提升质量的论文 文章目录 前言一、NeRF On-the-go&#xff1a;利用不确定性落地真实世界&#xff08;CVPR24&#xff09;摘要1.DINOv2特征的不确定性预测2.NeRF中干扰物去除的不确定性3.优化4. Dilated Patch 扩大采样5.实验结果 二、Pixel-GS:像素感知的梯度密…

关于nginx标准配置参数介绍

标准配置参数&#xff1a; user root;#配置用户或者组&#xff0c;默认为nobody worker_processes 4;#允许生成的进程数&#xff0c;默认为1 项目中nginx.conf配置文件 user root; worker_processes 4; //最大的进程数&#xff0c;要看服务器的内核是多少核的&#xff0…

Excel“取消工作表保护”忘记密码并恢复原始密码

文章目录 1.前言2.破解步骤3. 最终效果4.参考文献 1.前言 有时候别人发来的Excel中有些表格不能编辑&#xff0c;提示如下&#xff0c;但是又不知道原始密码 2.破解步骤 1、打开您需要破解保护密码的Excel文件&#xff1b; 2、依次点击菜单栏上的视图—宏----录制宏&#xf…

解决k8s分布式集群,子节点加入到主节点失败的问题

1.问题情况 Master主节点在 使用 kubeadm init 成功进行初始化后&#xff0c;如下所示 Your Kubernetes control-plane has initialized successfully!To start using your cluster, you need to run the following as a regular user:mkdir -p $HOME/.kubesudo cp -i /etc/k…

【Qt】 常用控件QLCDNumber

常用控件QLCDNumber QLCDNumber是一个专门用来显示数字的控件&#xff0c;类似于“老式计算机”的效果。 QLCDNumber的属性 属性说明 intValue QLCDNumber 显⽰的数字值(int). value QLCDNumber 显⽰的数字值(double). 和 intValue 是联动的. 例如给 value 设为 1.5, i…