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);
        }
    }

 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值