Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f9df4d64 authored by Oleg Blinnikov's avatar Oleg Blinnikov Committed by Android (Google) Code Review
Browse files

Merge "Backup & Restore display topology" into main

parents bfa852e0 d1e22d4f
Loading
Loading
Loading
Loading
+5 −2
Original line number Diff line number Diff line
@@ -35,7 +35,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;

import com.android.server.backup.Flags;
import com.android.server.display.DisplayBackupHelper;
import com.android.server.notification.NotificationBackupHelper;

import com.google.android.collect.Sets;
@@ -67,6 +67,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
    private static final String APP_GENDER_HELPER = "app_gender";
    private static final String COMPANION_HELPER = "companion";
    private static final String SYSTEM_GENDER_HELPER = "system_gender";
    private static final String DISPLAY_HELPER = "display";

    // These paths must match what the WallpaperManagerService uses.  The leaf *_FILENAME
    // are also used in the full-backup file format, so must not change unless steps are
@@ -104,7 +105,8 @@ public class SystemBackupAgent extends BackupAgentHelper {
                    APP_LOCALES_HELPER,
                    COMPANION_HELPER,
                    APP_GENDER_HELPER,
                    SYSTEM_GENDER_HELPER);
                    SYSTEM_GENDER_HELPER,
                    DISPLAY_HELPER);

    /** Helpers that are enabled for full, non-system users. */
    private static final Set<String> sEligibleHelpersForNonSystemUser =
@@ -146,6 +148,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
        addHelperIfEligibleForUser(COMPANION_HELPER, new CompanionBackupHelper(mUserId));
        addHelperIfEligibleForUser(SYSTEM_GENDER_HELPER,
                new SystemGrammaticalGenderBackupHelper(mUserId));
        addHelperIfEligibleForUser(DISPLAY_HELPER, new DisplayBackupHelper(mUserId));
    }

    @Override
+137 −0
Original line number Diff line number Diff line
/*
 * Copyright 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.display;


import android.annotation.Nullable;
import android.app.backup.BlobBackupHelper;
import android.hardware.display.DisplayManagerInternal;
import android.util.AtomicFile;
import android.util.AtomicFileOutputStream;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.utils.DebugUtils;

import java.io.IOException;

/**
 * Display manager specific information backup helper. Backs-up the entire files for the given
 * user.
 * @hide
 */
public class DisplayBackupHelper extends BlobBackupHelper {
    private static final String TAG = "DisplayBackupHelper";

    // current schema of the backup state blob
    private static final int BLOB_VERSION = 1;

    // key under which the data blob is committed to back up
    private static final String KEY_DISPLAY = "display";

    // To enable these logs, run:
    // adb shell setprop persist.log.tag.DisplayBackupHelper DEBUG
    // adb reboot
    private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);

    private final int mUserId;
    private final Injector mInjector;

    /**
     * Construct a helper to manage backup/restore of entire files within Display Manager.
     *
     * @param userId  id of the user for which backup will be done.
     */
    public DisplayBackupHelper(int userId) {
        this(userId, new Injector());
    }

    @VisibleForTesting
    DisplayBackupHelper(int userId, Injector injector) {
        super(BLOB_VERSION, KEY_DISPLAY);
        mUserId = userId;
        mInjector = injector;
    }

    @Override
    protected byte[] getBackupPayload(String key) {
        if (!KEY_DISPLAY.equals(key) || !mInjector.isDisplayTopologyFlagEnabled()) {
            return null;
        }
        try {
            var result = mInjector.readTopologyFile(mUserId);
            Slog.i(TAG, "getBackupPayload for " + key + " done, size=" + result.length);
            return result;
        } catch (IOException e) {
            if (DEBUG) Slog.d(TAG, "Skip topology backup", e);
            return null;
        }
    }

    @Override
    protected void applyRestoredPayload(String key, byte[] payload) {
        if (!KEY_DISPLAY.equals(key) || !mInjector.isDisplayTopologyFlagEnabled()) {
            return;
        }
        try (var oStream = mInjector.writeTopologyFile(mUserId)) {
            oStream.write(payload);
            oStream.markSuccess();
            Slog.i(TAG, "applyRestoredPayload for " + key + " size=" + payload.length
                    + " to " + oStream);
        } catch (IOException e) {
            Slog.e(TAG, "applyRestoredPayload failed", e);
            return;
        }
        var displayManagerInternal = mInjector.getDisplayManagerInternal();
        if (displayManagerInternal == null) {
            Slog.e(TAG, "DisplayManagerInternal is null");
            return;
        }

        displayManagerInternal.reloadTopologies(mUserId);
    }

    @VisibleForTesting
    static class Injector {
        private final boolean mIsDisplayTopologyEnabled =
                new DisplayManagerFlags().isDisplayTopologyEnabled();

        boolean isDisplayTopologyFlagEnabled() {
            return mIsDisplayTopologyEnabled;
        }

        @Nullable
        DisplayManagerInternal getDisplayManagerInternal() {
            return LocalServices.getService(DisplayManagerInternal.class);
        }

        byte[] readTopologyFile(int userId) throws IOException {
            return getTopologyFile(userId).readFully();
        }

        AtomicFileOutputStream writeTopologyFile(int userId) throws IOException {
            return new AtomicFileOutputStream(getTopologyFile(userId));
        }

        private AtomicFile getTopologyFile(int userId) {
            return new AtomicFile(DisplayTopologyXmlStore.getUserTopologyFile(userId),
                    /*commitTag=*/ "topology-state");
        }
    };
}
+86 −0
Original line number Diff line number Diff line
/*
 * Copyright 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.display

import androidx.test.filters.SmallTest
import android.hardware.display.DisplayManagerInternal
import android.util.AtomicFileOutputStream
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import org.mockito.kotlin.verify
import org.mockito.kotlin.never

@SmallTest
class DisplayBackupHelperTest {
    private val mockInjector = mock<DisplayBackupHelper.Injector>()
    private val mockDmsInternal = mock<DisplayManagerInternal>()
    private val mockWriteTopologyFile = mock<AtomicFileOutputStream>()
    private val byteArray = byteArrayOf(0b00000001, 0b00000010, 0b00000011)
    private val helper = createBackupHelper(0, byteArray)

    @Test
    fun testBackupDisplayReturnsBytes() {
        assertThat(helper.getBackupPayload("display")).isEqualTo(byteArray)
    }

    @Test
    fun testBackupSomethingReturnsNull() {
        assertThat(helper.getBackupPayload("something")).isNull()
    }

    @Test
    fun testBackupDisplayReturnsNullWhenFlagDisabled() {
        whenever(mockInjector.isDisplayTopologyFlagEnabled()).thenReturn(false)
        assertThat(helper.getBackupPayload("display")).isNull()
    }

    @Test
    fun testRestoreDisplay() {
        helper.applyRestoredPayload("display", byteArray)
        verify(mockWriteTopologyFile).write(byteArray)
        verify(mockWriteTopologyFile).markSuccess()
        verify(mockDmsInternal).reloadTopologies(0)
    }

    @Test
    fun testRestoreSomethingDoesNothing() {
        helper.applyRestoredPayload("something", byteArray)
        verify(mockWriteTopologyFile, never()).write(byteArray)
        verify(mockWriteTopologyFile, never()).markSuccess()
        verify(mockDmsInternal, never()).reloadTopologies(0)
    }

    @Test
    fun testRestoreDisplayDoesNothingWhenFlagDisabled() {
        whenever(mockInjector.isDisplayTopologyFlagEnabled()).thenReturn(false)
        helper.applyRestoredPayload("display", byteArray)
        verify(mockWriteTopologyFile, never()).write(byteArray)
        verify(mockWriteTopologyFile, never()).markSuccess()
        verify(mockDmsInternal, never()).reloadTopologies(0)
    }

    fun createBackupHelper(userId: Int, topologyToBackup: ByteArray): DisplayBackupHelper {
        whenever(mockInjector.getDisplayManagerInternal()).thenReturn(mockDmsInternal)
        whenever(mockInjector.readTopologyFile(userId)).thenReturn(topologyToBackup)
        whenever(mockInjector.writeTopologyFile(userId)).thenReturn(mockWriteTopologyFile)
        whenever(mockInjector.isDisplayTopologyFlagEnabled()).thenReturn(true)

        return DisplayBackupHelper(userId, mockInjector)
    }
}
 No newline at end of file
+8 −4
Original line number Diff line number Diff line
@@ -93,7 +93,8 @@ public class SystemBackupAgentTest {
                        "app_locales",
                        "app_gender",
                        "companion",
                        "system_gender");
                        "system_gender",
                        "display");
    }

    @Test
@@ -118,7 +119,8 @@ public class SystemBackupAgentTest {
                        "app_locales",
                        "app_gender",
                        "companion",
                        "system_gender");
                        "system_gender",
                        "display");
    }

    @Test
@@ -136,7 +138,8 @@ public class SystemBackupAgentTest {
                        "app_locales",
                        "companion",
                        "app_gender",
                        "system_gender");
                        "system_gender",
                        "display");
    }

    @Test
@@ -158,7 +161,8 @@ public class SystemBackupAgentTest {
                        "shortcut_manager",
                        "companion",
                        "app_gender",
                        "system_gender");
                        "system_gender",
                        "display");
    }

    @Test