blob: 6bc7ba6499accb4adbba487a559f6e2cad5af9ce [file] [log] [blame]
/*
* Copyright (C) 2021 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.devicepolicy;
import android.os.UserHandle;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.util.JournaledFile;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
/**
* Class for dealing with Device Policy Manager Service version upgrades.
* Initially, this class is responsible for upgrading the "device_policies.xml" file upon
* platform version upgrade.
*
* It is useful for policies which have a different default for an upgrading device than a
* newly-configured device (for example, the admin can grant sensors-related permissions by
* default on existing fully-managed devices that upgrade to Android S, but on devices set up
* with Android S the value of the policy is set explicitly during set-up).
*
* Practically, it's useful for changes to the data model of the {@code DevicePolicyData} and
* {@code ActiveAdmin} classes.
*
* To add a new upgrade step:
* (1) Increase the {@code DPMS_VERSION} constant in {@code DevicePolicyManagerService} by one.
* (2) Add an if statement in {@code upgradePolicy} comparing the version, performing the upgrade
* step and setting the value of {@code currentVersion} to the newly-incremented version.
* (3) Add a test in {@code PolicyVersionUpgraderTest}.
*/
public class PolicyVersionUpgrader {
private static final String LOG_TAG = "DevicePolicyManager";
private static final boolean VERBOSE_LOG = DevicePolicyManagerService.VERBOSE_LOG;
private final PolicyUpgraderDataProvider mProvider;
public PolicyVersionUpgrader(PolicyUpgraderDataProvider provider) {
mProvider = provider;
}
/**
* Performs the upgrade steps for all users on the system.
*
* @param allUsers List of all user IDs on the system, including disabled users, as well as
* managed profile user IDs.
* @param dpmsVersion The version to upgrade to.
*/
public void upgradePolicy(int dpmsVersion) {
int oldVersion = readVersion();
if (oldVersion >= dpmsVersion) {
Slog.i(LOG_TAG, String.format("Current version %d, latest version %d, not upgrading.",
oldVersion, dpmsVersion));
return;
}
final int[] allUsers = mProvider.getUsersForUpgrade();
//NOTE: The current version is provided in case the XML file format changes in a
// non-backwards-compatible way, so that DeviceAdminData could load it with
// old tags, for example.
final SparseArray<DevicePolicyData> allUsersData = loadAllUsersData(allUsers, oldVersion);
int currentVersion = oldVersion;
if (currentVersion == 0) {
Slog.i(LOG_TAG, String.format("Upgrading from version %d", currentVersion));
// The first upgrade (from no version to version 1) is to overwrite
// the "active-password" tag in case it was left around.
currentVersion = 1;
}
if (currentVersion == 1) {
Slog.i(LOG_TAG, String.format("Upgrading from version %d", currentVersion));
// This upgrade step is for Device Owner scenario only: For devices upgrading to S,
// if there is a device owner, it retains the ability to control sensors-related
// permission grants.
for (int userId : allUsers) {
DevicePolicyData userData = allUsersData.get(userId);
if (userData == null) {
continue;
}
for (ActiveAdmin admin : userData.mAdminList) {
if (mProvider.isDeviceOwner(userId, admin.info.getComponent())) {
Slog.i(LOG_TAG, String.format(
"Marking Device Owner in user %d for permission grant ", userId));
admin.mAdminCanGrantSensorsPermissions = true;
}
}
}
currentVersion = 2;
}
writePoliciesAndVersion(allUsers, allUsersData, currentVersion);
}
private void writePoliciesAndVersion(int[] allUsers, SparseArray<DevicePolicyData> allUsersData,
int currentVersion) {
boolean allWritesSuccessful = true;
for (int user : allUsers) {
allWritesSuccessful = allWritesSuccessful && writeDataForUser(user,
allUsersData.get(user));
}
if (allWritesSuccessful) {
writeVersion(currentVersion);
} else {
Slog.e(LOG_TAG, String.format("Error: Failed upgrading policies to version %d",
currentVersion));
}
}
private SparseArray<DevicePolicyData> loadAllUsersData(int[] allUsers, int loadVersion) {
final SparseArray<DevicePolicyData> allUsersData = new SparseArray<>();
for (int user: allUsers) {
allUsersData.append(user, loadDataForUser(user, loadVersion));
}
return allUsersData;
}
private DevicePolicyData loadDataForUser(int userId, int loadVersion) {
DevicePolicyData policy = new DevicePolicyData(userId);
DevicePolicyData.load(policy,
!mProvider.storageManagerIsFileBasedEncryptionEnabled(),
mProvider.makeDevicePoliciesJournaledFile(userId),
mProvider.getAdminInfoSupplier(userId),
mProvider.getOwnerComponent(userId));
return policy;
}
private boolean writeDataForUser(int userId, DevicePolicyData policy) {
return DevicePolicyData.store(
policy,
mProvider.makeDevicePoliciesJournaledFile(userId),
!mProvider.storageManagerIsFileBasedEncryptionEnabled());
}
private JournaledFile getVersionFile() {
return mProvider.makePoliciesVersionJournaledFile(UserHandle.USER_SYSTEM);
}
private int readVersion() {
JournaledFile versionFile = getVersionFile();
File file = versionFile.chooseForRead();
if (VERBOSE_LOG) {
Slog.v(LOG_TAG, "Loading version from " + file);
}
try {
String versionString = Files.readAllLines(
file.toPath(), Charset.defaultCharset()).get(0);
return Integer.parseInt(versionString);
} catch (IOException | NumberFormatException | IndexOutOfBoundsException e) {
Slog.e(LOG_TAG, "Error reading version", e);
return 0;
}
}
private void writeVersion(int version) {
JournaledFile versionFile = getVersionFile();
File file = versionFile.chooseForWrite();
if (VERBOSE_LOG) {
Slog.v(LOG_TAG, String.format("Writing new version to: %s", file));
}
try {
byte[] versionBytes = String.format("%d", version).getBytes();
Files.write(file.toPath(), versionBytes);
versionFile.commit();
} catch (IOException e) {
Slog.e(LOG_TAG, String.format("Writing version %d failed: %s", version), e);
versionFile.rollback();
}
}
}