|  | /* | 
|  | * Copyright (C) 2014 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.annotation.Nullable; | 
|  | import android.app.admin.SystemUpdateInfo; | 
|  | import android.app.admin.SystemUpdatePolicy; | 
|  | import android.content.ComponentName; | 
|  | import android.content.pm.PackageManagerInternal; | 
|  | import android.content.pm.UserInfo; | 
|  | import android.os.Environment; | 
|  | import android.os.UserHandle; | 
|  | import android.os.UserManager; | 
|  | import android.os.UserManagerInternal; | 
|  | import android.util.ArrayMap; | 
|  | import android.util.AtomicFile; | 
|  | import android.util.Log; | 
|  | import android.util.Pair; | 
|  | import android.util.Slog; | 
|  | import android.util.SparseArray; | 
|  | import android.util.Xml; | 
|  |  | 
|  | import com.android.internal.annotations.VisibleForTesting; | 
|  | import com.android.internal.util.FastXmlSerializer; | 
|  |  | 
|  | import org.xmlpull.v1.XmlPullParser; | 
|  | import org.xmlpull.v1.XmlPullParserException; | 
|  | import org.xmlpull.v1.XmlSerializer; | 
|  |  | 
|  | import java.io.File; | 
|  | import java.io.FileOutputStream; | 
|  | import java.io.IOException; | 
|  | import java.io.InputStream; | 
|  | import java.io.PrintWriter; | 
|  | import java.nio.charset.StandardCharsets; | 
|  | import java.time.LocalDate; | 
|  | import java.util.List; | 
|  | import java.util.Map; | 
|  | import java.util.Objects; | 
|  | import java.util.Set; | 
|  |  | 
|  | import libcore.io.IoUtils; | 
|  |  | 
|  | /** | 
|  | * Stores and restores state for the Device and Profile owners and related device-wide information. | 
|  | * By definition there can be only one device owner, but there may be a profile owner for each user. | 
|  | * | 
|  | * <p>This class is thread safe, so individual methods can safely be called without locking. | 
|  | * However, caller must still synchronize on their side to ensure integrity between multiple calls. | 
|  | */ | 
|  | class Owners { | 
|  | private static final String TAG = "DevicePolicyManagerService"; | 
|  |  | 
|  | private static final boolean DEBUG = false; // DO NOT SUBMIT WITH TRUE | 
|  |  | 
|  | private static final String DEVICE_OWNER_XML_LEGACY = "device_owner.xml"; | 
|  |  | 
|  | // XML storing device owner info, system update policy and pending OTA update information. | 
|  | private static final String DEVICE_OWNER_XML = "device_owner_2.xml"; | 
|  |  | 
|  | private static final String PROFILE_OWNER_XML = "profile_owner.xml"; | 
|  |  | 
|  | private static final String TAG_ROOT = "root"; | 
|  |  | 
|  | private static final String TAG_DEVICE_OWNER = "device-owner"; | 
|  | private static final String TAG_DEVICE_INITIALIZER = "device-initializer"; | 
|  | private static final String TAG_SYSTEM_UPDATE_POLICY = "system-update-policy"; | 
|  | private static final String TAG_FREEZE_PERIOD_RECORD = "freeze-record"; | 
|  | private static final String TAG_PENDING_OTA_INFO = "pending-ota-info"; | 
|  | private static final String TAG_PROFILE_OWNER = "profile-owner"; | 
|  | // Holds "context" for device-owner, this must not be show up before device-owner. | 
|  | private static final String TAG_DEVICE_OWNER_CONTEXT = "device-owner-context"; | 
|  |  | 
|  | private static final String ATTR_NAME = "name"; | 
|  | private static final String ATTR_PACKAGE = "package"; | 
|  | private static final String ATTR_COMPONENT_NAME = "component"; | 
|  | private static final String ATTR_REMOTE_BUGREPORT_URI = "remoteBugreportUri"; | 
|  | private static final String ATTR_REMOTE_BUGREPORT_HASH = "remoteBugreportHash"; | 
|  | private static final String ATTR_USERID = "userId"; | 
|  | private static final String ATTR_USER_RESTRICTIONS_MIGRATED = "userRestrictionsMigrated"; | 
|  | private static final String ATTR_FREEZE_RECORD_START = "start"; | 
|  | private static final String ATTR_FREEZE_RECORD_END = "end"; | 
|  |  | 
|  | private final UserManager mUserManager; | 
|  | private final UserManagerInternal mUserManagerInternal; | 
|  | private final PackageManagerInternal mPackageManagerInternal; | 
|  |  | 
|  | // Internal state for the device owner package. | 
|  | private OwnerInfo mDeviceOwner; | 
|  |  | 
|  | private int mDeviceOwnerUserId = UserHandle.USER_NULL; | 
|  |  | 
|  | // Internal state for the profile owner packages. | 
|  | private final ArrayMap<Integer, OwnerInfo> mProfileOwners = new ArrayMap<>(); | 
|  |  | 
|  | // Local system update policy controllable by device owner. | 
|  | private SystemUpdatePolicy mSystemUpdatePolicy; | 
|  | private LocalDate mSystemUpdateFreezeStart; | 
|  | private LocalDate mSystemUpdateFreezeEnd; | 
|  |  | 
|  | // Pending OTA info if there is one. | 
|  | @Nullable | 
|  | private SystemUpdateInfo mSystemUpdateInfo; | 
|  |  | 
|  | private final Object mLock = new Object(); | 
|  | private final Injector mInjector; | 
|  |  | 
|  | public Owners(UserManager userManager, | 
|  | UserManagerInternal userManagerInternal, | 
|  | PackageManagerInternal packageManagerInternal) { | 
|  | this(userManager, userManagerInternal, packageManagerInternal, new Injector()); | 
|  | } | 
|  |  | 
|  | @VisibleForTesting | 
|  | Owners(UserManager userManager, | 
|  | UserManagerInternal userManagerInternal, | 
|  | PackageManagerInternal packageManagerInternal, | 
|  | Injector injector) { | 
|  | mUserManager = userManager; | 
|  | mUserManagerInternal = userManagerInternal; | 
|  | mPackageManagerInternal = packageManagerInternal; | 
|  | mInjector = injector; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Load configuration from the disk. | 
|  | */ | 
|  | void load() { | 
|  | synchronized (mLock) { | 
|  | // First, try to read from the legacy file. | 
|  | final File legacy = getLegacyConfigFile(); | 
|  |  | 
|  | final List<UserInfo> users = mUserManager.getUsers(true); | 
|  |  | 
|  | if (readLegacyOwnerFileLocked(legacy)) { | 
|  | if (DEBUG) { | 
|  | Log.d(TAG, "Legacy config file found."); | 
|  | } | 
|  |  | 
|  | // Legacy file exists, write to new files and remove the legacy one. | 
|  | writeDeviceOwner(); | 
|  | for (int userId : getProfileOwnerKeys()) { | 
|  | writeProfileOwner(userId); | 
|  | } | 
|  | if (DEBUG) { | 
|  | Log.d(TAG, "Deleting legacy config file"); | 
|  | } | 
|  | if (!legacy.delete()) { | 
|  | Slog.e(TAG, "Failed to remove the legacy setting file"); | 
|  | } | 
|  | } else { | 
|  | // No legacy file, read from the new format files. | 
|  | new DeviceOwnerReadWriter().readFromFileLocked(); | 
|  |  | 
|  | for (UserInfo ui : users) { | 
|  | new ProfileOwnerReadWriter(ui.id).readFromFileLocked(); | 
|  | } | 
|  | } | 
|  | mUserManagerInternal.setDeviceManaged(hasDeviceOwner()); | 
|  | for (UserInfo ui : users) { | 
|  | mUserManagerInternal.setUserManaged(ui.id, hasProfileOwner(ui.id)); | 
|  | } | 
|  | if (hasDeviceOwner() && hasProfileOwner(getDeviceOwnerUserId())) { | 
|  | Slog.w(TAG, String.format("User %d has both DO and PO, which is not supported", | 
|  | getDeviceOwnerUserId())); | 
|  | } | 
|  | pushToPackageManagerLocked(); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void pushToPackageManagerLocked() { | 
|  | final SparseArray<String> po = new SparseArray<>(); | 
|  | for (int i = mProfileOwners.size() - 1; i >= 0; i--) { | 
|  | po.put(mProfileOwners.keyAt(i), mProfileOwners.valueAt(i).packageName); | 
|  | } | 
|  | mPackageManagerInternal.setDeviceAndProfileOwnerPackages( | 
|  | mDeviceOwnerUserId, (mDeviceOwner != null ? mDeviceOwner.packageName : null), | 
|  | po); | 
|  | } | 
|  |  | 
|  | String getDeviceOwnerPackageName() { | 
|  | synchronized (mLock) { | 
|  | return mDeviceOwner != null ? mDeviceOwner.packageName : null; | 
|  | } | 
|  | } | 
|  |  | 
|  | int getDeviceOwnerUserId() { | 
|  | synchronized (mLock) { | 
|  | return mDeviceOwnerUserId; | 
|  | } | 
|  | } | 
|  |  | 
|  | @Nullable | 
|  | Pair<Integer, ComponentName> getDeviceOwnerUserIdAndComponent() { | 
|  | synchronized (mLock) { | 
|  | if (mDeviceOwner == null) { | 
|  | return null; | 
|  | } else { | 
|  | return Pair.create(mDeviceOwnerUserId, mDeviceOwner.admin); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | String getDeviceOwnerName() { | 
|  | synchronized (mLock) { | 
|  | return mDeviceOwner != null ? mDeviceOwner.name : null; | 
|  | } | 
|  | } | 
|  |  | 
|  | ComponentName getDeviceOwnerComponent() { | 
|  | synchronized (mLock) { | 
|  | return mDeviceOwner != null ? mDeviceOwner.admin : null; | 
|  | } | 
|  | } | 
|  |  | 
|  | String getDeviceOwnerRemoteBugreportUri() { | 
|  | synchronized (mLock) { | 
|  | return mDeviceOwner != null ? mDeviceOwner.remoteBugreportUri : null; | 
|  | } | 
|  | } | 
|  |  | 
|  | String getDeviceOwnerRemoteBugreportHash() { | 
|  | synchronized (mLock) { | 
|  | return mDeviceOwner != null ? mDeviceOwner.remoteBugreportHash : null; | 
|  | } | 
|  | } | 
|  |  | 
|  | void setDeviceOwner(ComponentName admin, String ownerName, int userId) { | 
|  | if (userId < 0) { | 
|  | Slog.e(TAG, "Invalid user id for device owner user: " + userId); | 
|  | return; | 
|  | } | 
|  | synchronized (mLock) { | 
|  | // For a newly set DO, there's no need for migration. | 
|  | setDeviceOwnerWithRestrictionsMigrated(admin, ownerName, userId, | 
|  | /* userRestrictionsMigrated =*/ true); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Note this should be only called during migration.  Normally when DO is set, | 
|  | // userRestrictionsMigrated should always be true. | 
|  | void setDeviceOwnerWithRestrictionsMigrated(ComponentName admin, String ownerName, int userId, | 
|  | boolean userRestrictionsMigrated) { | 
|  | synchronized (mLock) { | 
|  | mDeviceOwner = new OwnerInfo(ownerName, admin, userRestrictionsMigrated, | 
|  | /* remoteBugreportUri =*/ null, /* remoteBugreportHash =*/ null); | 
|  | mDeviceOwnerUserId = userId; | 
|  |  | 
|  | mUserManagerInternal.setDeviceManaged(true); | 
|  | pushToPackageManagerLocked(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void clearDeviceOwner() { | 
|  | synchronized (mLock) { | 
|  | mDeviceOwner = null; | 
|  | mDeviceOwnerUserId = UserHandle.USER_NULL; | 
|  |  | 
|  | mUserManagerInternal.setDeviceManaged(false); | 
|  | pushToPackageManagerLocked(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void setProfileOwner(ComponentName admin, String ownerName, int userId) { | 
|  | synchronized (mLock) { | 
|  | // For a newly set PO, there's no need for migration. | 
|  | mProfileOwners.put(userId, new OwnerInfo(ownerName, admin, | 
|  | /* userRestrictionsMigrated =*/ true, /* remoteBugreportUri =*/ null, | 
|  | /* remoteBugreportHash =*/ null)); | 
|  | mUserManagerInternal.setUserManaged(userId, true); | 
|  | pushToPackageManagerLocked(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void removeProfileOwner(int userId) { | 
|  | synchronized (mLock) { | 
|  | mProfileOwners.remove(userId); | 
|  | mUserManagerInternal.setUserManaged(userId, false); | 
|  | pushToPackageManagerLocked(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void transferProfileOwner(ComponentName target, int userId) { | 
|  | synchronized (mLock) { | 
|  | final OwnerInfo ownerInfo = mProfileOwners.get(userId); | 
|  | final OwnerInfo newOwnerInfo = new OwnerInfo(target.getPackageName(), target, | 
|  | ownerInfo.userRestrictionsMigrated, ownerInfo.remoteBugreportUri, | 
|  | ownerInfo.remoteBugreportHash); | 
|  | mProfileOwners.put(userId, newOwnerInfo); | 
|  | pushToPackageManagerLocked(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void transferDeviceOwnership(ComponentName target) { | 
|  | synchronized (mLock) { | 
|  | // We don't set a name because it's not used anyway. | 
|  | // See DevicePolicyManagerService#getDeviceOwnerName | 
|  | mDeviceOwner = new OwnerInfo(null, target, | 
|  | mDeviceOwner.userRestrictionsMigrated, mDeviceOwner.remoteBugreportUri, | 
|  | mDeviceOwner.remoteBugreportHash); | 
|  | pushToPackageManagerLocked(); | 
|  | } | 
|  | } | 
|  |  | 
|  | ComponentName getProfileOwnerComponent(int userId) { | 
|  | synchronized (mLock) { | 
|  | OwnerInfo profileOwner = mProfileOwners.get(userId); | 
|  | return profileOwner != null ? profileOwner.admin : null; | 
|  | } | 
|  | } | 
|  |  | 
|  | String getProfileOwnerName(int userId) { | 
|  | synchronized (mLock) { | 
|  | OwnerInfo profileOwner = mProfileOwners.get(userId); | 
|  | return profileOwner != null ? profileOwner.name : null; | 
|  | } | 
|  | } | 
|  |  | 
|  | String getProfileOwnerPackage(int userId) { | 
|  | synchronized (mLock) { | 
|  | OwnerInfo profileOwner = mProfileOwners.get(userId); | 
|  | return profileOwner != null ? profileOwner.packageName : null; | 
|  | } | 
|  | } | 
|  |  | 
|  | Set<Integer> getProfileOwnerKeys() { | 
|  | synchronized (mLock) { | 
|  | return mProfileOwners.keySet(); | 
|  | } | 
|  | } | 
|  |  | 
|  | SystemUpdatePolicy getSystemUpdatePolicy() { | 
|  | synchronized (mLock) { | 
|  | return mSystemUpdatePolicy; | 
|  | } | 
|  | } | 
|  |  | 
|  | void setSystemUpdatePolicy(SystemUpdatePolicy systemUpdatePolicy) { | 
|  | synchronized (mLock) { | 
|  | mSystemUpdatePolicy = systemUpdatePolicy; | 
|  | } | 
|  | } | 
|  |  | 
|  | void clearSystemUpdatePolicy() { | 
|  | synchronized (mLock) { | 
|  | mSystemUpdatePolicy = null; | 
|  | } | 
|  | } | 
|  |  | 
|  | Pair<LocalDate, LocalDate> getSystemUpdateFreezePeriodRecord() { | 
|  | synchronized (mLock) { | 
|  | return new Pair<>(mSystemUpdateFreezeStart, mSystemUpdateFreezeEnd); | 
|  | } | 
|  | } | 
|  |  | 
|  | String getSystemUpdateFreezePeriodRecordAsString() { | 
|  | StringBuilder freezePeriodRecord = new StringBuilder(); | 
|  | freezePeriodRecord.append("start: "); | 
|  | if (mSystemUpdateFreezeStart != null) { | 
|  | freezePeriodRecord.append(mSystemUpdateFreezeStart.toString()); | 
|  | } else { | 
|  | freezePeriodRecord.append("null"); | 
|  | } | 
|  | freezePeriodRecord.append("; end: "); | 
|  | if (mSystemUpdateFreezeEnd != null) { | 
|  | freezePeriodRecord.append(mSystemUpdateFreezeEnd.toString()); | 
|  | } else { | 
|  | freezePeriodRecord.append("null"); | 
|  | } | 
|  | return freezePeriodRecord.toString(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns {@code true} if the freeze period record is changed, {@code false} otherwise. | 
|  | */ | 
|  | boolean setSystemUpdateFreezePeriodRecord(LocalDate start, LocalDate end) { | 
|  | boolean changed = false; | 
|  | synchronized (mLock) { | 
|  | if (!Objects.equals(mSystemUpdateFreezeStart, start)) { | 
|  | mSystemUpdateFreezeStart = start; | 
|  | changed = true; | 
|  | } | 
|  | if (!Objects.equals(mSystemUpdateFreezeEnd, end)) { | 
|  | mSystemUpdateFreezeEnd = end; | 
|  | changed = true; | 
|  | } | 
|  | } | 
|  | return changed; | 
|  | } | 
|  |  | 
|  | boolean hasDeviceOwner() { | 
|  | synchronized (mLock) { | 
|  | return mDeviceOwner != null; | 
|  | } | 
|  | } | 
|  |  | 
|  | boolean isDeviceOwnerUserId(int userId) { | 
|  | synchronized (mLock) { | 
|  | return mDeviceOwner != null && mDeviceOwnerUserId == userId; | 
|  | } | 
|  | } | 
|  |  | 
|  | boolean hasProfileOwner(int userId) { | 
|  | synchronized (mLock) { | 
|  | return getProfileOwnerComponent(userId) != null; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return true if user restrictions need to be migrated for DO. | 
|  | */ | 
|  | boolean getDeviceOwnerUserRestrictionsNeedsMigration() { | 
|  | synchronized (mLock) { | 
|  | return mDeviceOwner != null && !mDeviceOwner.userRestrictionsMigrated; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return true if user restrictions need to be migrated for PO. | 
|  | */ | 
|  | boolean getProfileOwnerUserRestrictionsNeedsMigration(int userId) { | 
|  | synchronized (mLock) { | 
|  | OwnerInfo profileOwner = mProfileOwners.get(userId); | 
|  | return profileOwner != null && !profileOwner.userRestrictionsMigrated; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** Sets the user restrictions migrated flag, and also writes to the file. */ | 
|  | void setDeviceOwnerUserRestrictionsMigrated() { | 
|  | synchronized (mLock) { | 
|  | if (mDeviceOwner != null) { | 
|  | mDeviceOwner.userRestrictionsMigrated = true; | 
|  | } | 
|  | writeDeviceOwner(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** Sets the remote bugreport uri and hash, and also writes to the file. */ | 
|  | void setDeviceOwnerRemoteBugreportUriAndHash(String remoteBugreportUri, | 
|  | String remoteBugreportHash) { | 
|  | synchronized (mLock) { | 
|  | if (mDeviceOwner != null) { | 
|  | mDeviceOwner.remoteBugreportUri = remoteBugreportUri; | 
|  | mDeviceOwner.remoteBugreportHash = remoteBugreportHash; | 
|  | } | 
|  | writeDeviceOwner(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** Sets the user restrictions migrated flag, and also writes to the file.  */ | 
|  | void setProfileOwnerUserRestrictionsMigrated(int userId) { | 
|  | synchronized (mLock) { | 
|  | OwnerInfo profileOwner = mProfileOwners.get(userId); | 
|  | if (profileOwner != null) { | 
|  | profileOwner.userRestrictionsMigrated = true; | 
|  | } | 
|  | writeProfileOwner(userId); | 
|  | } | 
|  | } | 
|  |  | 
|  | private boolean readLegacyOwnerFileLocked(File file) { | 
|  | if (!file.exists()) { | 
|  | // Already migrated or the device has no owners. | 
|  | return false; | 
|  | } | 
|  | try { | 
|  | InputStream input = new AtomicFile(file).openRead(); | 
|  | XmlPullParser parser = Xml.newPullParser(); | 
|  | parser.setInput(input, StandardCharsets.UTF_8.name()); | 
|  | int type; | 
|  | while ((type=parser.next()) != XmlPullParser.END_DOCUMENT) { | 
|  | if (type!=XmlPullParser.START_TAG) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | String tag = parser.getName(); | 
|  | if (tag.equals(TAG_DEVICE_OWNER)) { | 
|  | String name = parser.getAttributeValue(null, ATTR_NAME); | 
|  | String packageName = parser.getAttributeValue(null, ATTR_PACKAGE); | 
|  | mDeviceOwner = new OwnerInfo(name, packageName, | 
|  | /* userRestrictionsMigrated =*/ false, /* remoteBugreportUri =*/ null, | 
|  | /* remoteBugreportHash =*/ null); | 
|  | mDeviceOwnerUserId = UserHandle.USER_SYSTEM; | 
|  | } else if (tag.equals(TAG_DEVICE_INITIALIZER)) { | 
|  | // Deprecated tag | 
|  | } else if (tag.equals(TAG_PROFILE_OWNER)) { | 
|  | String profileOwnerPackageName = parser.getAttributeValue(null, ATTR_PACKAGE); | 
|  | String profileOwnerName = parser.getAttributeValue(null, ATTR_NAME); | 
|  | String profileOwnerComponentStr = | 
|  | parser.getAttributeValue(null, ATTR_COMPONENT_NAME); | 
|  | int userId = Integer.parseInt(parser.getAttributeValue(null, ATTR_USERID)); | 
|  | OwnerInfo profileOwnerInfo = null; | 
|  | if (profileOwnerComponentStr != null) { | 
|  | ComponentName admin = ComponentName.unflattenFromString( | 
|  | profileOwnerComponentStr); | 
|  | if (admin != null) { | 
|  | profileOwnerInfo = new OwnerInfo(profileOwnerName, admin, | 
|  | /* userRestrictionsMigrated =*/ false, null, null); | 
|  | } else { | 
|  | // This shouldn't happen but switch from package name -> component name | 
|  | // might have written bad device owner files. b/17652534 | 
|  | Slog.e(TAG, "Error parsing device-owner file. Bad component name " + | 
|  | profileOwnerComponentStr); | 
|  | } | 
|  | } | 
|  | if (profileOwnerInfo == null) { | 
|  | profileOwnerInfo = new OwnerInfo(profileOwnerName, profileOwnerPackageName, | 
|  | /* userRestrictionsMigrated =*/ false, | 
|  | /* remoteBugreportUri =*/ null, /* remoteBugreportHash =*/ null); | 
|  | } | 
|  | mProfileOwners.put(userId, profileOwnerInfo); | 
|  | } else if (TAG_SYSTEM_UPDATE_POLICY.equals(tag)) { | 
|  | mSystemUpdatePolicy = SystemUpdatePolicy.restoreFromXml(parser); | 
|  | } else { | 
|  | throw new XmlPullParserException( | 
|  | "Unexpected tag in device owner file: " + tag); | 
|  | } | 
|  | } | 
|  | input.close(); | 
|  | } catch (XmlPullParserException|IOException e) { | 
|  | Slog.e(TAG, "Error parsing device-owner file", e); | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void writeDeviceOwner() { | 
|  | synchronized (mLock) { | 
|  | if (DEBUG) { | 
|  | Log.d(TAG, "Writing to device owner file"); | 
|  | } | 
|  | new DeviceOwnerReadWriter().writeToFileLocked(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void writeProfileOwner(int userId) { | 
|  | synchronized (mLock) { | 
|  | if (DEBUG) { | 
|  | Log.d(TAG, "Writing to profile owner file for user " + userId); | 
|  | } | 
|  | new ProfileOwnerReadWriter(userId).writeToFileLocked(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Saves the given {@link SystemUpdateInfo} if it is different from the existing one, or if | 
|  | * none exists. | 
|  | * | 
|  | * @return Whether the saved system update information has changed. | 
|  | */ | 
|  | boolean saveSystemUpdateInfo(@Nullable SystemUpdateInfo newInfo) { | 
|  | synchronized (mLock) { | 
|  | // Check if we already have the same update information. | 
|  | if (Objects.equals(newInfo, mSystemUpdateInfo)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | mSystemUpdateInfo = newInfo; | 
|  | new DeviceOwnerReadWriter().writeToFileLocked(); | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | @Nullable | 
|  | public SystemUpdateInfo getSystemUpdateInfo() { | 
|  | synchronized (mLock) { | 
|  | return mSystemUpdateInfo; | 
|  | } | 
|  | } | 
|  |  | 
|  | private abstract static class FileReadWriter { | 
|  | private final File mFile; | 
|  |  | 
|  | protected FileReadWriter(File file) { | 
|  | mFile = file; | 
|  | } | 
|  |  | 
|  | abstract boolean shouldWrite(); | 
|  |  | 
|  | void writeToFileLocked() { | 
|  | if (!shouldWrite()) { | 
|  | if (DEBUG) { | 
|  | Log.d(TAG, "No need to write to " + mFile); | 
|  | } | 
|  | // No contents, remove the file. | 
|  | if (mFile.exists()) { | 
|  | if (DEBUG) { | 
|  | Log.d(TAG, "Deleting existing " + mFile); | 
|  | } | 
|  | if (!mFile.delete()) { | 
|  | Slog.e(TAG, "Failed to remove " + mFile.getPath()); | 
|  | } | 
|  | } | 
|  | return; | 
|  | } | 
|  | if (DEBUG) { | 
|  | Log.d(TAG, "Writing to " + mFile); | 
|  | } | 
|  |  | 
|  | final AtomicFile f = new AtomicFile(mFile); | 
|  | FileOutputStream outputStream = null; | 
|  | try { | 
|  | outputStream = f.startWrite(); | 
|  | final XmlSerializer out = new FastXmlSerializer(); | 
|  | out.setOutput(outputStream, StandardCharsets.UTF_8.name()); | 
|  |  | 
|  | // Root tag | 
|  | out.startDocument(null, true); | 
|  | out.startTag(null, TAG_ROOT); | 
|  |  | 
|  | // Actual content | 
|  | writeInner(out); | 
|  |  | 
|  | // Close root | 
|  | out.endTag(null, TAG_ROOT); | 
|  | out.endDocument(); | 
|  | out.flush(); | 
|  |  | 
|  | // Commit the content. | 
|  | f.finishWrite(outputStream); | 
|  | outputStream = null; | 
|  |  | 
|  | } catch (IOException e) { | 
|  | Slog.e(TAG, "Exception when writing", e); | 
|  | if (outputStream != null) { | 
|  | f.failWrite(outputStream); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void readFromFileLocked() { | 
|  | if (!mFile.exists()) { | 
|  | if (DEBUG) { | 
|  | Log.d(TAG, "" + mFile + " doesn't exist"); | 
|  | } | 
|  | return; | 
|  | } | 
|  | if (DEBUG) { | 
|  | Log.d(TAG, "Reading from " + mFile); | 
|  | } | 
|  | final AtomicFile f = new AtomicFile(mFile); | 
|  | InputStream input = null; | 
|  | try { | 
|  | input = f.openRead(); | 
|  | final XmlPullParser parser = Xml.newPullParser(); | 
|  | parser.setInput(input, StandardCharsets.UTF_8.name()); | 
|  |  | 
|  | int type; | 
|  | int depth = 0; | 
|  | while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { | 
|  | switch (type) { | 
|  | case XmlPullParser.START_TAG: | 
|  | depth++; | 
|  | break; | 
|  | case XmlPullParser.END_TAG: | 
|  | depth--; | 
|  | // fallthrough | 
|  | default: | 
|  | continue; | 
|  | } | 
|  | // Check the root tag | 
|  | final String tag = parser.getName(); | 
|  | if (depth == 1) { | 
|  | if (!TAG_ROOT.equals(tag)) { | 
|  | Slog.e(TAG, "Invalid root tag: " + tag); | 
|  | return; | 
|  | } | 
|  | continue; | 
|  | } | 
|  | // readInner() will only see START_TAG at depth >= 2. | 
|  | if (!readInner(parser, depth, tag)) { | 
|  | return; // Error | 
|  | } | 
|  | } | 
|  | } catch (XmlPullParserException | IOException e) { | 
|  | Slog.e(TAG, "Error parsing owners information file", e); | 
|  | } finally { | 
|  | IoUtils.closeQuietly(input); | 
|  | } | 
|  | } | 
|  |  | 
|  | abstract void writeInner(XmlSerializer out) throws IOException; | 
|  |  | 
|  | abstract boolean readInner(XmlPullParser parser, int depth, String tag); | 
|  | } | 
|  |  | 
|  | private class DeviceOwnerReadWriter extends FileReadWriter { | 
|  |  | 
|  | protected DeviceOwnerReadWriter() { | 
|  | super(getDeviceOwnerFile()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | boolean shouldWrite() { | 
|  | return (mDeviceOwner != null) || (mSystemUpdatePolicy != null) | 
|  | || (mSystemUpdateInfo != null); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | void writeInner(XmlSerializer out) throws IOException { | 
|  | if (mDeviceOwner != null) { | 
|  | mDeviceOwner.writeToXml(out, TAG_DEVICE_OWNER); | 
|  | out.startTag(null, TAG_DEVICE_OWNER_CONTEXT); | 
|  | out.attribute(null, ATTR_USERID, String.valueOf(mDeviceOwnerUserId)); | 
|  | out.endTag(null, TAG_DEVICE_OWNER_CONTEXT); | 
|  | } | 
|  |  | 
|  | if (mSystemUpdatePolicy != null) { | 
|  | out.startTag(null, TAG_SYSTEM_UPDATE_POLICY); | 
|  | mSystemUpdatePolicy.saveToXml(out); | 
|  | out.endTag(null, TAG_SYSTEM_UPDATE_POLICY); | 
|  | } | 
|  |  | 
|  | if (mSystemUpdateInfo != null) { | 
|  | mSystemUpdateInfo.writeToXml(out, TAG_PENDING_OTA_INFO); | 
|  | } | 
|  |  | 
|  | if (mSystemUpdateFreezeStart != null || mSystemUpdateFreezeEnd != null) { | 
|  | out.startTag(null, TAG_FREEZE_PERIOD_RECORD); | 
|  | if (mSystemUpdateFreezeStart != null) { | 
|  | out.attribute(null, ATTR_FREEZE_RECORD_START, | 
|  | mSystemUpdateFreezeStart.toString()); | 
|  | } | 
|  | if (mSystemUpdateFreezeEnd != null) { | 
|  | out.attribute(null, ATTR_FREEZE_RECORD_END, mSystemUpdateFreezeEnd.toString()); | 
|  | } | 
|  | out.endTag(null, TAG_FREEZE_PERIOD_RECORD); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | boolean readInner(XmlPullParser parser, int depth, String tag) { | 
|  | if (depth > 2) { | 
|  | return true; // Ignore | 
|  | } | 
|  | switch (tag) { | 
|  | case TAG_DEVICE_OWNER: | 
|  | mDeviceOwner = OwnerInfo.readFromXml(parser); | 
|  | mDeviceOwnerUserId = UserHandle.USER_SYSTEM; // Set default | 
|  | break; | 
|  | case TAG_DEVICE_OWNER_CONTEXT: { | 
|  | final String userIdString = | 
|  | parser.getAttributeValue(null, ATTR_USERID); | 
|  | try { | 
|  | mDeviceOwnerUserId = Integer.parseInt(userIdString); | 
|  | } catch (NumberFormatException e) { | 
|  | Slog.e(TAG, "Error parsing user-id " + userIdString); | 
|  | } | 
|  | break; | 
|  | } | 
|  | case TAG_DEVICE_INITIALIZER: | 
|  | // Deprecated tag | 
|  | break; | 
|  | case TAG_SYSTEM_UPDATE_POLICY: | 
|  | mSystemUpdatePolicy = SystemUpdatePolicy.restoreFromXml(parser); | 
|  | break; | 
|  | case TAG_PENDING_OTA_INFO: | 
|  | mSystemUpdateInfo = SystemUpdateInfo.readFromXml(parser); | 
|  | break; | 
|  | case TAG_FREEZE_PERIOD_RECORD: | 
|  | String startDate = parser.getAttributeValue(null, ATTR_FREEZE_RECORD_START); | 
|  | String endDate = parser.getAttributeValue(null, ATTR_FREEZE_RECORD_END); | 
|  | if (startDate != null && endDate != null) { | 
|  | mSystemUpdateFreezeStart = LocalDate.parse(startDate); | 
|  | mSystemUpdateFreezeEnd = LocalDate.parse(endDate); | 
|  | if (mSystemUpdateFreezeStart.isAfter(mSystemUpdateFreezeEnd)) { | 
|  | Slog.e(TAG, "Invalid system update freeze record loaded"); | 
|  | mSystemUpdateFreezeStart = null; | 
|  | mSystemUpdateFreezeEnd = null; | 
|  | } | 
|  | } | 
|  | break; | 
|  | default: | 
|  | Slog.e(TAG, "Unexpected tag: " + tag); | 
|  | return false; | 
|  |  | 
|  | } | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | private class ProfileOwnerReadWriter extends FileReadWriter { | 
|  | private final int mUserId; | 
|  |  | 
|  | ProfileOwnerReadWriter(int userId) { | 
|  | super(getProfileOwnerFile(userId)); | 
|  | mUserId = userId; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | boolean shouldWrite() { | 
|  | return mProfileOwners.get(mUserId) != null; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | void writeInner(XmlSerializer out) throws IOException { | 
|  | final OwnerInfo profileOwner = mProfileOwners.get(mUserId); | 
|  | if (profileOwner != null) { | 
|  | profileOwner.writeToXml(out, TAG_PROFILE_OWNER); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | boolean readInner(XmlPullParser parser, int depth, String tag) { | 
|  | if (depth > 2) { | 
|  | return true; // Ignore | 
|  | } | 
|  | switch (tag) { | 
|  | case TAG_PROFILE_OWNER: | 
|  | mProfileOwners.put(mUserId, OwnerInfo.readFromXml(parser)); | 
|  | break; | 
|  | default: | 
|  | Slog.e(TAG, "Unexpected tag: " + tag); | 
|  | return false; | 
|  |  | 
|  | } | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | static class OwnerInfo { | 
|  | public final String name; | 
|  | public final String packageName; | 
|  | public final ComponentName admin; | 
|  | public boolean userRestrictionsMigrated; | 
|  | public String remoteBugreportUri; | 
|  | public String remoteBugreportHash; | 
|  |  | 
|  | public OwnerInfo(String name, String packageName, boolean userRestrictionsMigrated, | 
|  | String remoteBugreportUri, String remoteBugreportHash) { | 
|  | this.name = name; | 
|  | this.packageName = packageName; | 
|  | this.admin = new ComponentName(packageName, ""); | 
|  | this.userRestrictionsMigrated = userRestrictionsMigrated; | 
|  | this.remoteBugreportUri = remoteBugreportUri; | 
|  | this.remoteBugreportHash = remoteBugreportHash; | 
|  | } | 
|  |  | 
|  | public OwnerInfo(String name, ComponentName admin, boolean userRestrictionsMigrated, | 
|  | String remoteBugreportUri, String remoteBugreportHash) { | 
|  | this.name = name; | 
|  | this.admin = admin; | 
|  | this.packageName = admin.getPackageName(); | 
|  | this.userRestrictionsMigrated = userRestrictionsMigrated; | 
|  | this.remoteBugreportUri = remoteBugreportUri; | 
|  | this.remoteBugreportHash = remoteBugreportHash; | 
|  | } | 
|  |  | 
|  | public void writeToXml(XmlSerializer out, String tag) throws IOException { | 
|  | out.startTag(null, tag); | 
|  | out.attribute(null, ATTR_PACKAGE, packageName); | 
|  | if (name != null) { | 
|  | out.attribute(null, ATTR_NAME, name); | 
|  | } | 
|  | if (admin != null) { | 
|  | out.attribute(null, ATTR_COMPONENT_NAME, admin.flattenToString()); | 
|  | } | 
|  | out.attribute(null, ATTR_USER_RESTRICTIONS_MIGRATED, | 
|  | String.valueOf(userRestrictionsMigrated)); | 
|  | if (remoteBugreportUri != null) { | 
|  | out.attribute(null, ATTR_REMOTE_BUGREPORT_URI, remoteBugreportUri); | 
|  | } | 
|  | if (remoteBugreportHash != null) { | 
|  | out.attribute(null, ATTR_REMOTE_BUGREPORT_HASH, remoteBugreportHash); | 
|  | } | 
|  | out.endTag(null, tag); | 
|  | } | 
|  |  | 
|  | public static OwnerInfo readFromXml(XmlPullParser parser) { | 
|  | final String packageName = parser.getAttributeValue(null, ATTR_PACKAGE); | 
|  | final String name = parser.getAttributeValue(null, ATTR_NAME); | 
|  | final String componentName = | 
|  | parser.getAttributeValue(null, ATTR_COMPONENT_NAME); | 
|  | final String userRestrictionsMigratedStr = | 
|  | parser.getAttributeValue(null, ATTR_USER_RESTRICTIONS_MIGRATED); | 
|  | final boolean userRestrictionsMigrated = | 
|  | ("true".equals(userRestrictionsMigratedStr)); | 
|  | final String remoteBugreportUri = parser.getAttributeValue(null, | 
|  | ATTR_REMOTE_BUGREPORT_URI); | 
|  | final String remoteBugreportHash = parser.getAttributeValue(null, | 
|  | ATTR_REMOTE_BUGREPORT_HASH); | 
|  |  | 
|  | // Has component name?  If so, return [name, component] | 
|  | if (componentName != null) { | 
|  | final ComponentName admin = ComponentName.unflattenFromString(componentName); | 
|  | if (admin != null) { | 
|  | return new OwnerInfo(name, admin, userRestrictionsMigrated, | 
|  | remoteBugreportUri, remoteBugreportHash); | 
|  | } else { | 
|  | // This shouldn't happen but switch from package name -> component name | 
|  | // might have written bad device owner files. b/17652534 | 
|  | Slog.e(TAG, "Error parsing owner file. Bad component name " + | 
|  | componentName); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Else, build with [name, package] | 
|  | return new OwnerInfo(name, packageName, userRestrictionsMigrated, remoteBugreportUri, | 
|  | remoteBugreportHash); | 
|  | } | 
|  |  | 
|  | public void dump(String prefix, PrintWriter pw) { | 
|  | pw.println(prefix + "admin=" + admin); | 
|  | pw.println(prefix + "name=" + name); | 
|  | pw.println(prefix + "package=" + packageName); | 
|  | } | 
|  | } | 
|  |  | 
|  | public void dump(String prefix, PrintWriter pw) { | 
|  | boolean needBlank = false; | 
|  | if (mDeviceOwner != null) { | 
|  | pw.println(prefix + "Device Owner: "); | 
|  | mDeviceOwner.dump(prefix + "  ", pw); | 
|  | pw.println(prefix + "  User ID: " + mDeviceOwnerUserId); | 
|  | needBlank = true; | 
|  | } | 
|  | if (mSystemUpdatePolicy != null) { | 
|  | if (needBlank) { | 
|  | pw.println(); | 
|  | } | 
|  | pw.println(prefix + "System Update Policy: " + mSystemUpdatePolicy); | 
|  | needBlank = true; | 
|  | } | 
|  | if (mProfileOwners != null) { | 
|  | for (Map.Entry<Integer, OwnerInfo> entry : mProfileOwners.entrySet()) { | 
|  | if (needBlank) { | 
|  | pw.println(); | 
|  | } | 
|  | pw.println(prefix + "Profile Owner (User " + entry.getKey() + "): "); | 
|  | entry.getValue().dump(prefix + "  ", pw); | 
|  | needBlank = true; | 
|  | } | 
|  | } | 
|  | if (mSystemUpdateInfo != null) { | 
|  | if (needBlank) { | 
|  | pw.println(); | 
|  | } | 
|  | pw.println(prefix + "Pending System Update: " + mSystemUpdateInfo); | 
|  | needBlank = true; | 
|  | } | 
|  | if (mSystemUpdateFreezeStart != null || mSystemUpdateFreezeEnd != null) { | 
|  | if (needBlank) { | 
|  | pw.println(); | 
|  | } | 
|  | pw.println(prefix + "System update freeze record: " | 
|  | + getSystemUpdateFreezePeriodRecordAsString()); | 
|  | needBlank = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | @VisibleForTesting | 
|  | File getLegacyConfigFile() { | 
|  | return new File(mInjector.environmentGetDataSystemDirectory(), DEVICE_OWNER_XML_LEGACY); | 
|  | } | 
|  |  | 
|  | @VisibleForTesting | 
|  | File getDeviceOwnerFile() { | 
|  | return new File(mInjector.environmentGetDataSystemDirectory(), DEVICE_OWNER_XML); | 
|  | } | 
|  |  | 
|  | @VisibleForTesting | 
|  | File getProfileOwnerFile(int userId) { | 
|  | return new File(mInjector.environmentGetUserSystemDirectory(userId), PROFILE_OWNER_XML); | 
|  | } | 
|  |  | 
|  | @VisibleForTesting | 
|  | public static class Injector { | 
|  | File environmentGetDataSystemDirectory() { | 
|  | return Environment.getDataSystemDirectory(); | 
|  | } | 
|  |  | 
|  | File environmentGetUserSystemDirectory(int userId) { | 
|  | return Environment.getUserSystemDirectory(userId); | 
|  | } | 
|  | } | 
|  | } |