Android 开发者选项-模拟辅助显示设备

概述

在这里插入图片描述

    在Android开发中,模拟辅助显示设备通常指的是通过Android开发者选项来设置的一种虚拟显示设备,它允许开发者在一个设备上模拟另一个设备的显示特性。这种功能对于测试应用程序在不同屏幕尺寸、分辨率和DPI(每英寸点数)下的表现非常有用。另一个作用是, 它可以通过特定的开发技巧和功能来充当副屏,实现多屏显示的效果。这种功能在开发测试、多任务处理以及特定应用场景(如车载系统)中非常有用。

使用

如何打开

  1. 打开设置
  2. 进入开发者选项 (方法自行查阅)
  3. 选择 模拟辅助显示设备
    在这里插入图片描述

在这里插入图片描述
这个窗口会以浮动窗口的方式显示在上层, 支持的基本操作:

  • 拖动到任意位置
  • 缩放 (长按或多触摸)

开关的代码实现方式

参考系统设置的源代码:

布局文件: packages/apps/Settings/res/xml/development_prefs.xml

        <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" />

列表和对应的值: frameworks/base/packages/SettingsLib/res/values/arrays.xml

    <!-- Titles for overlay display devices preference. [CHAR LIMIT=35] -->
    <string-array name="overlay_display_devices_entries">
        <item>None</item>
        <item>480p</item>
        <item>480p (secure)</item>
        <item>720p</item>
        <item>720p (secure)</item>
        <item>1080p</item>
        <item>1080p (secure)</item>
        <item>4K</item>
        <item>4K (secure)</item>
        <item>4K (upscaled)</item>
        <item>4K (upscaled, secure)</item>
        <item>720p, 1080p (dual screen)</item>
    </string-array>

    <!-- Values for overlay display devices preference. -->
    <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>

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

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

       CharSequence[] values = mOverlayDisplayDevices.getEntryValues();
       for (int i = 0; i < values.length; i++) {
           if (value.contentEquals(values[i])) {
               mOverlayDisplayDevices.setValueIndex(i);
               mOverlayDisplayDevices.setSummary(mOverlayDisplayDevices.getEntries()[i]);
               return;
           }
       }
       mOverlayDisplayDevices.setValueIndex(0);
       mOverlayDisplayDevices.setSummary(mOverlayDisplayDevices.getEntries()[0]);
   }

查看和设置当前配置的值:

# 查看
settings get global overlay_display_devices                                                                                                                                                                                                           
720x480/142

# 配置
settings put global overlay_display_devices 1920x1080/320

在应用获得权限的前提下, 可以尝试使用代码来获取和设置:

//获取
  String overlay_display_devices = Settings.Global.getString(getContentResolver(), "overlay_display_devices");
//设置
  Settings.Global.putString(getContentResolver(), "overlay_display_devices", "");

系统部分的处理:

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

    @Override
    public void registerLocked() {
        super.registerLocked();

        getHandler().post(new Runnable() {
            @Override
            public void run() {
                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();
        }
    }

    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(";")) {
            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("\\|")) {
                    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);
                    boolean secure = flagString != null && flagString.contains(",secure");

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

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

创建OverlayDisplayDevice, 并显示

    /**
     * Functions as a handle for overlay display devices which are created and
     * destroyed asynchronously.
     *
     * Guarded by the {@link DisplayManagerService.SyncRoot} lock.
     */
    private final class OverlayDisplayHandle implements OverlayDisplayWindow.Listener {
        private static final int DEFAULT_MODE_INDEX = 0;

        private final String mName;
        private final List<OverlayMode> mModes;
        private final int mGravity;
        private final boolean mSecure;
        private final int mNumber;

        private OverlayDisplayWindow mWindow;
        private OverlayDisplayDevice mDevice;
        private int mActiveMode;

        public OverlayDisplayHandle(String name, List<OverlayMode> modes, int gravity,
                boolean secure, int number) {
            mName = name;
            mModes = modes;
            mGravity = gravity;
            mSecure = secure;
            mNumber = number;

            mActiveMode = 0;

            showLocked();
        }

        private void showLocked() {
            mUiHandler.post(mShowRunnable);
        }

        public void dismissLocked() {
            mUiHandler.removeCallbacks(mShowRunnable);
            mUiHandler.post(mDismissRunnable);
        }

        private void onActiveModeChangedLocked(int index) {
            mUiHandler.removeCallbacks(mResizeRunnable);
            mActiveMode = index;
            if (mWindow != null) {
                mUiHandler.post(mResizeRunnable);
            }
        }

        // Called on the UI thread.
        @Override
        public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate,
                long presentationDeadlineNanos, int state) {
            synchronized (getSyncRoot()) {
                IBinder displayToken = SurfaceControl.createDisplay(mName, mSecure);
                mDevice = new OverlayDisplayDevice(displayToken, mName, mModes, mActiveMode,
                        DEFAULT_MODE_INDEX, refreshRate, presentationDeadlineNanos,
                        mSecure, state, surfaceTexture, mNumber) {
                    @Override
                    public void onModeChangedLocked(int index) {
                        onActiveModeChangedLocked(index);
                    }
                };

                sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED);
            }
        }

参考

Android双屏异显(Presentation)与后台动态配置副屏内容
Android双屏异显以及原理分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值