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

news2024/11/23 23:45:35

前言

上一篇我们通过为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);
        }
    }

 }

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Qt5编译qmqtt库使用MQTT协议连接华为云IOT完成数据上传与交互

一、前言 随着物联网技术的发展,越来越多的设备通过网络互相连接,形成了庞大的智能系统。这些系统能够收集、分析并响应各种数据,从而实现自动化控制和智能化管理。在这个背景下,MQTT 成为了一个广泛使用的轻量级消息传输协议,特别适用于资源受限的环境,如移动应用或远程…

WebSocket 实现:注解与原生方式对比

WebSocket 作为一种在单个长连接上进行全双工、双向通信的协议&#xff0c;已经成为现代Web应用中实现实时通信的重要技术。本文将探讨如何使用注解和原生方式来实现 WebSocket&#xff0c;并对这两种方法进行比较。 一、注解方式实现 WebSocket 在许多现代Java框架中&#x…

GBJ406-ASEMI无人机专用GBJ406

编辑&#xff1a;ll GBJ406-ASEMI无人机专用GBJ406 型号&#xff1a;GBJ406 品牌&#xff1a;ASEMI 封装&#xff1a;GBJ-4 批号&#xff1a;2024 现货&#xff1a;50000 最大重复峰值反向电压&#xff1a;600V 最大正向平均整流电流(Vdss)&#xff1a;4A 功率(Pd)&am…

43.【C语言】指针(重难点)(F)

目录 15.二级指针 *定义 *演示 16.三级以及多级指针 *三级指针的定义 *多级指针的定义 17.指针数组 *定义 *代码 18.指针数组模拟二维数组 往期推荐 15.二级指针 *定义 之前讲的指针全是一级指针 int a 1; int *pa &a;//一级指针 如果写成 int a 1; int *pa &a…

MES生产执行系统源码,支持 SaaS 多租户,技术架构:springboot + vue-element-plus-admin

MES的定义与功能 MES是制造业中一种重要的管理信息系统&#xff0c;用于协调和监控整个生产过程。它通过收集、分析和处理各种生产数据&#xff0c;实现对生产流程的实时跟踪和监控&#xff0c;并为决策者提供准确的数据支持。MES涵盖了工厂运营、计划排程、质量管理、设备维护…

AI时代下的智慧体育, 用科技赋能体育创新

在科技飞速发展的今天&#xff0c;人工智能&#xff08;AI&#xff09;已成为推动各行各业创新的重要力量。体育&#xff0c;作为人类文明的重要组成部分&#xff0c;同样在AI的浪潮中迎来了新的变革机遇。AI时代下的智慧体育&#xff0c;不再局限于传统的运动模式&#xff0c;…

Spring Boot集成Devtools实现热更新?

1.什么Devtools&#xff1f; DevTools是开发者工具集&#xff0c;主要用于简化开发过程中的热部署问题。 热部署是指在开发过程中&#xff0c;当代码发生变化时&#xff0c;无需手动重启应用&#xff0c;系统能够自动检测并重新加载修改后的代码&#xff0c;大大提高了开发效率…

量化投资策略与技术学习PART2:量化选股之风格轮动

市场上的投资者是有偏好的&#xff0c;有时候偏好于价值股&#xff0c;有时候偏好于成长股&#xff0c;有时偏于大盘&#xff0c;有时又偏于小盘&#xff0c;由于投资者的这种不同的交易行为&#xff0c;形成了市场风格&#xff0c;本节主要研究如何判断市场风格&#xff0c;以…

MyBatis介绍(1)

前言 MyBatis 是一个半 ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;它内部封装了 JDBC&#xff0c;开发时只需要关注 SQL 语句本身&#xff0c;不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。程序员直接编写原生态 sql&#xff0c;可以…

【java报错已解决】error: metadata-generation-failed

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 一、问题描述1.1 报错示例1.2 报错分析1.3 解决思路二、解决方法2.1 方法一&#xff1a;检查环境变量2.2 步骤二&…

嵌入式学习Day30---Linux软件编程---进程间的通信

目录 一、Linux操作ipc对象&#xff08;内存文件&#xff09;的命令 1.1.查看命令 1.ipcs 2.ipcs -q&#xff08;查看信息队列&#xff09; 3.ipcs -m&#xff08;查看共享内存&#xff09; 4.ipcs -s&#xff08;查看信号灯&#xff09; 1.2.删除命令 1.ipcrm -q id 2.ipc…

conda虚拟环境中pip的混淆

在conda的虚拟环境中&#xff0c;会在<PATH>\Anaconda\envs\<ENV_NAME>\Scripts目录下存在 pip.exe 和pip3.exe. 如果存在多个虚拟环境是&#xff0c;加上conda自带的python版本&#xff0c;系统中存在多个pip和pip3指令&#xff0c;在执行安装的时候&#xff0c;…

【AI 绘画】 文生图图生图(基于diffusers)

AI 绘画- 文生图&图生图&#xff08;基于diffusers&#xff09; 1. 效果展示 本次测试主要结果展示如下&#xff1a; SDXL文生图 可爱Lora 2. 基本原理 模型基本原理介绍如下 stable diffusion首先训练一个自编码器&#xff0c;学习将图像数据压缩为低维表示。通过使…

VINS-Fusion的点云转换成ego-planner能用的点云

背景 2013年智在飞翔比赛&#xff1a; RoboMaster | 无人飞行器智能感知技术竞赛https://www.robomaster.com/zh-CN/robo/drone?djifromnav_drone 用vins-fusion来定位&#xff0c;他自己会生成点云数据。 进一步用ego-planner来路径规划和避障&#xff0c;需要用到vins-f…

mpls静态lsp实验

实验需求 R1、R2和R3之间已经部署了IGP协议&#xff0c;故192.168.10.0/24与192.168.20.0/24网络之间已经能够互访。现要求通过配置 静态LSP&#xff0c;使得这两个网络之间能基于MPLS进行互访&#xff0c;标签分配如图 组网图 实验思路 1、R1、R2和R3之间已经部署了IGP协议…

非科班出身的你,如何转行AI算法工程师?

想从其他行业转行到算法工程师的人&#xff0c;无外乎以下几个原因&#xff1a; 现在工资太低工作没有前景对现在的工作没有热情对算法工程师很感兴趣 那么&#xff0c;如何成功转行&#xff1f;给大家整理一些学习方式。 1&#xff09;数据结构和算法&#xff1a;推荐大家使…

自动化测试系列:接口自动化测试框架--05通过邮件发送测试结果的封装

框架功能介绍 1.自动整理接口测试用例&#xff1a;只需使用抓包工具&#xff0c;将需要接口请求另存为HAR文件&#xff0c;执行har2excel.bat即可自动生成接口请求测试用例&#xff0c;同时将接口请求的host地址写入到配置文件&#xff08;测试用例仅生成正向用例&#xff0c;…

前端css线性渐变

background: linear-gradient(90deg,red,green); 1.支持多颜色渐变 2.支持多方向渐变 to left to top left 3.支持角度90deg <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA…

CDH 6.3.1 史上最全安装手册

因项目需要CDH&#xff0c;经过十来次的重复安装&#xff0c;反复踩坑、填坑、验证&#xff0c;终于了今日的成功。 基础设施 设置主机 Nodecloudera-scm-servercloudera-scm-agent操作系统cpu内存cdh-guance01✅✅Centos7.44核16Gcdh-guance02❎✅Centos7.44核16Gcdh-guanc…

Cesium.js:webGIS领域的翘楚,开源全球地理空间数据可视化框架.

说起数据可视化/数字孪生开发&#xff0c;少不了webGIS&#xff0c;聊起webGIS不得不提大名鼎鼎的Cesium.js框架。 CesiumJS是一个用于创建地理空间应用程序的开源JavaScript库。它提供了丰富的地图和地理空间数据的可视化功能&#xff0c;可以用于构建基于地理位置的3D地图、…