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

Commit 0371b06a authored by Al Sutton's avatar Al Sutton
Browse files

Add ability to exclude settings from being restored.

Bug: 143129051
Test: atest SettingsProviderTest
Change-Id: Id5d367b0fb8386dbae4eb3e29c8c1e9ba3c547d6
parent b6314a1a
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2019 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.
  -->

<!-- These are arrays of settings which should not be restored to this device -->
<resources>
    <string-array name="restore_blocked_device_specific_settings" />
    <string-array name="restore_blocked_global_settings" />
    <string-array name="restore_blocked_secure_settings" />
    <string-array name="restore_blocked_system_settings" />
</resources>
 No newline at end of file
+25 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
  <overlayable name="SettingsToNotRestore">
    <policy type="product|system|vendor">
      <item type="array" name="restore_blocked_device_specific_settings" />
      <item type="array" name="restore_blocked_global_settings" />
      <item type="array" name="restore_blocked_secure_settings" />
      <item type="array" name="restore_blocked_system_settings" />
    </policy>
  </overlayable>
</resources>
+121 −22
Original line number Diff line number Diff line
@@ -66,6 +66,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.time.DateTimeException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
@@ -250,7 +251,13 @@ public class SettingsBackupAgent extends BackupAgentHelper {

    @Override
    public void onRestore(BackupDataInput data, int appVersionCode,
            ParcelFileDescriptor newState) throws IOException {
            ParcelFileDescriptor newState) {
        throw new RuntimeException("SettingsBackupAgent has been migrated to use key exclusion");
    }

    @Override
    public void onRestore(BackupDataInput data, long appVersionCode,
            ParcelFileDescriptor newState, Set<String> dynamicBlockList) throws IOException {

        if (DEBUG) {
            Log.d(TAG, "onRestore(): appVersionCode: " + appVersionCode
@@ -266,7 +273,7 @@ public class SettingsBackupAgent extends BackupAgentHelper {
        }

        // versionCode of com.android.providers.settings corresponds to SDK_INT
        mRestoredFromSdkInt = appVersionCode;
        mRestoredFromSdkInt = (int) appVersionCode;

        HashSet<String> movedToGlobal = new HashSet<String>();
        Settings.System.getMovedToGlobalSettings(movedToGlobal);
@@ -292,16 +299,29 @@ public class SettingsBackupAgent extends BackupAgentHelper {
            switch (key) {
                case KEY_SYSTEM :
                    restoreSettings(data, Settings.System.CONTENT_URI, movedToGlobal,
                            movedToSecure);
                            movedToSecure, R.array.restore_blocked_system_settings,
                            dynamicBlockList);
                    mSettingsHelper.applyAudioSettings();
                    break;

                case KEY_SECURE :
                    restoreSettings(data, Settings.Secure.CONTENT_URI, movedToGlobal, null);
                    restoreSettings(
                            data,
                            Settings.Secure.CONTENT_URI,
                            movedToGlobal,
                            null,
                            R.array.restore_blocked_secure_settings,
                            dynamicBlockList);
                    break;

                case KEY_GLOBAL :
                    restoreSettings(data, Settings.Global.CONTENT_URI, null, movedToSecure);
                    restoreSettings(
                            data,
                            Settings.Global.CONTENT_URI,
                            null,
                            movedToSecure,
                            R.array.restore_blocked_global_settings,
                            dynamicBlockList);
                    break;

                case KEY_WIFI_SUPPLICANT :
@@ -345,7 +365,10 @@ public class SettingsBackupAgent extends BackupAgentHelper {
                case KEY_DEVICE_SPECIFIC_CONFIG:
                    byte[] restoredDeviceSpecificConfig = new byte[size];
                    data.readEntityData(restoredDeviceSpecificConfig, 0, size);
                    restoreDeviceSpecificConfig(restoredDeviceSpecificConfig);
                    restoreDeviceSpecificConfig(
                            restoredDeviceSpecificConfig,
                            R.array.restore_blocked_device_specific_settings,
                            dynamicBlockList);
                    break;

                default :
@@ -394,14 +417,22 @@ public class SettingsBackupAgent extends BackupAgentHelper {
            byte[] buffer = new byte[nBytes];
            in.readFully(buffer, 0, nBytes);
            restoreSettings(buffer, nBytes, Settings.System.CONTENT_URI, movedToGlobal,
                    movedToSecure);
                    movedToSecure, R.array.restore_blocked_system_settings,
                    Collections.emptySet());

            // secure settings
            nBytes = in.readInt();
            if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of secure settings data");
            if (nBytes > buffer.length) buffer = new byte[nBytes];
            in.readFully(buffer, 0, nBytes);
            restoreSettings(buffer, nBytes, Settings.Secure.CONTENT_URI, movedToGlobal, null);
            restoreSettings(
                    buffer,
                    nBytes,
                    Settings.Secure.CONTENT_URI,
                    movedToGlobal,
                    null,
                    R.array.restore_blocked_secure_settings,
                    Collections.emptySet());

            // Global only if sufficiently new
            if (version >= FULL_BACKUP_ADDED_GLOBAL) {
@@ -411,7 +442,8 @@ public class SettingsBackupAgent extends BackupAgentHelper {
                in.readFully(buffer, 0, nBytes);
                movedToGlobal.clear();  // no redirection; this *is* the global namespace
                restoreSettings(buffer, nBytes, Settings.Global.CONTENT_URI, movedToGlobal,
                        movedToSecure);
                        movedToSecure, R.array.restore_blocked_global_settings,
                        Collections.emptySet());
            }

            // locale
@@ -612,8 +644,13 @@ public class SettingsBackupAgent extends BackupAgentHelper {
        return baos.toByteArray();
    }

    private void restoreSettings(BackupDataInput data, Uri contentUri,
            HashSet<String> movedToGlobal, Set<String> movedToSecure) {
    private void restoreSettings(
            BackupDataInput data,
            Uri contentUri,
            HashSet<String> movedToGlobal,
            Set<String> movedToSecure,
            int blockedSettingsArrayId,
            Set<String> dynamicBlockList) {
        byte[] settings = new byte[data.getDataSize()];
        try {
            data.readEntityData(settings, 0, settings.length);
@@ -621,16 +658,44 @@ public class SettingsBackupAgent extends BackupAgentHelper {
            Log.e(TAG, "Couldn't read entity data");
            return;
        }
        restoreSettings(settings, settings.length, contentUri, movedToGlobal, movedToSecure);
    }

    private void restoreSettings(byte[] settings, int bytes, Uri contentUri,
            HashSet<String> movedToGlobal, Set<String> movedToSecure) {
        restoreSettings(settings, 0, bytes, contentUri, movedToGlobal, movedToSecure);
    }

    private void restoreSettings(byte[] settings, int pos, int bytes, Uri contentUri,
                HashSet<String> movedToGlobal, Set<String> movedToSecure) {
        restoreSettings(
                settings,
                settings.length,
                contentUri,
                movedToGlobal,
                movedToSecure,
                blockedSettingsArrayId,
                dynamicBlockList);
    }

    private void restoreSettings(
            byte[] settings,
            int bytes,
            Uri contentUri,
            HashSet<String> movedToGlobal,
            Set<String> movedToSecure,
            int blockedSettingsArrayId,
            Set<String> dynamicBlockList) {
        restoreSettings(
                settings,
                0,
                bytes,
                contentUri,
                movedToGlobal,
                movedToSecure,
                blockedSettingsArrayId,
                dynamicBlockList);
    }

    private void restoreSettings(
            byte[] settings,
            int pos,
            int bytes,
            Uri contentUri,
            HashSet<String> movedToGlobal,
            Set<String> movedToSecure,
            int blockedSettingsArrayId,
            Set<String> dynamicBlockList) {
        if (DEBUG) {
            Log.i(TAG, "restoreSettings: " + contentUri);
        }
@@ -662,9 +727,20 @@ public class SettingsBackupAgent extends BackupAgentHelper {
        SettingsHelper settingsHelper = mSettingsHelper;
        ContentResolver cr = getContentResolver();

        final int whiteListSize = whitelist.length;
        for (int i = 0; i < whiteListSize; i++) {
            String key = whitelist[i];
        Set<String> blockedSettings = getBlockedSettings(blockedSettingsArrayId);

        for (String key : whitelist) {
            boolean isBlockedBySystem = blockedSettings != null && blockedSettings.contains(key);
            if (isBlockedBySystem || isBlockedByDynamicList(dynamicBlockList, contentUri,  key)) {
                Log.i(
                        TAG,
                        "Key "
                                + key
                                + " removed from restore by "
                                + (isBlockedBySystem ? "system" : "dynamic")
                                + " block list");
                continue;
            }

            String value = null;
            boolean hasValueToRestore = false;
@@ -722,6 +798,19 @@ public class SettingsBackupAgent extends BackupAgentHelper {
        }
    }

    private boolean isBlockedByDynamicList(Set<String> dynamicBlockList, Uri areaUri, String key) {
        String contentKey = Uri.withAppendedPath(areaUri, key).toString();
        return dynamicBlockList.contains(contentKey);
    }

    // There may be other sources of blocked settings, so I'm separating out this
    // code to make it easy to modify in the future.
    @VisibleForTesting
    protected Set<String> getBlockedSettings(int blockedSettingsArrayId) {
        String[] blockedSettings = getResources().getStringArray(blockedSettingsArrayId);
        return new HashSet<>(Arrays.asList(blockedSettings));
    }

    private boolean isValidSettingValue(String key, String value,
            Map<String, Validator> validators) {
        if (key == null || validators == null) {
@@ -998,10 +1087,13 @@ public class SettingsBackupAgent extends BackupAgentHelper {
     * Restore the device specific settings.
     *
     * @param data The byte array holding a backed up version of another devices settings.
     * @param blockedSettingsArrayId The string array resource holding the settings not to restore.
     * @param dynamicBlocklist The dynamic list of settings not to restore fed into this agent.
     * @return true if the restore succeeded, false if it was stopped.
     */
    @VisibleForTesting
    boolean restoreDeviceSpecificConfig(byte[] data) {
    boolean restoreDeviceSpecificConfig(byte[] data, int blockedSettingsArrayId,
            Set<String> dynamicBlocklist) {
        // We're using an AtomicInteger to wrap the position int and allow called methods to
        // modify it.
        AtomicInteger pos = new AtomicInteger(0);
@@ -1013,7 +1105,14 @@ public class SettingsBackupAgent extends BackupAgentHelper {

        int dataStart = pos.get();
        restoreSettings(
                data, dataStart, data.length, Settings.Secure.CONTENT_URI, null, null);
                data,
                dataStart,
                data.length,
                Settings.Secure.CONTENT_URI,
                null,
                null,
                blockedSettingsArrayId,
                dynamicBlocklist);

        updateWindowManagerIfNeeded(originalDensity);

+49 −5
Original line number Diff line number Diff line
@@ -37,14 +37,14 @@ import android.test.mock.MockContentResolver;

import androidx.test.runner.AndroidJUnit4;

import com.android.internal.annotations.VisibleForTesting;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -85,11 +85,32 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {

        assertEquals("Not all values backed up.", TEST_VALUES.keySet(), helper.mReadEntries);

        mAgentUnderTest.restoreDeviceSpecificConfig(settingsBackup);
        mAgentUnderTest.restoreDeviceSpecificConfig(
                settingsBackup,
                R.array.restore_blocked_device_specific_settings,
                Collections.emptySet());

        assertEquals("Not all values were restored.", TEST_VALUES, helper.mWrittenValues);
    }

    @Test
    public void testRoundTripDeviceSpecificSettingsWithBlock() throws IOException {
        TestSettingsHelper helper = new TestSettingsHelper(mContext);
        mAgentUnderTest.mSettingsHelper = helper;

        byte[] settingsBackup = mAgentUnderTest.getDeviceSpecificConfiguration();

        assertEquals("Not all values backed up.", TEST_VALUES.keySet(), helper.mReadEntries);
        mAgentUnderTest.setBlockedSettings(TEST_VALUES.keySet().toArray(new String[0]));

        mAgentUnderTest.restoreDeviceSpecificConfig(
                settingsBackup,
                R.array.restore_blocked_device_specific_settings,
                Collections.emptySet());

        assertTrue("Not all values were blocked.", helper.mWrittenValues.isEmpty());
    }

    @Test
    public void testGeneratedHeaderMatchesCurrentDevice() throws IOException {
        mAgentUnderTest.mSettingsHelper = new TestSettingsHelper(mContext);
@@ -148,7 +169,10 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {

        assertFalse(
                "Blocking isSourceAcceptable did not stop restore",
                mAgentUnderTest.restoreDeviceSpecificConfig(data));
                mAgentUnderTest.restoreDeviceSpecificConfig(
                        data,
                        R.array.restore_blocked_device_specific_settings,
                        Collections.emptySet()));
    }

    private byte[] generateUncorruptedHeader() throws IOException {
@@ -184,14 +208,34 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {
        }
    }

    private byte[] generateSingleKeyTestBackupData(String key, String value) throws IOException {
        try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
            os.write(SettingsBackupAgent.toByteArray(key));
            os.write(SettingsBackupAgent.toByteArray(value));
            return os.toByteArray();
        }
    }

    private static class TestFriendlySettingsBackupAgent extends SettingsBackupAgent {
        private Boolean mForcedDeviceInfoRestoreAcceptability = null;
        private String[] mBlockedSettings = null;

        void setForcedDeviceInfoRestoreAcceptability(boolean value) {
            mForcedDeviceInfoRestoreAcceptability = value;
        }

        @VisibleForTesting
        void setBlockedSettings(String... blockedSettings) {
            mBlockedSettings = blockedSettings;
        }

        @Override
        protected Set<String> getBlockedSettings(int blockedSettingsArrayId) {
            return mBlockedSettings == null
                    ? super.getBlockedSettings(blockedSettingsArrayId)
                    : new HashSet<>(Arrays.asList(mBlockedSettings));
        }

        @Override
        boolean isSourceAcceptable(byte[] data, AtomicInteger pos) {
            return mForcedDeviceInfoRestoreAcceptability == null
                    ? super.isSourceAcceptable(data, pos)