blob: 6ac0d89c74d2734c35b3e51f34aaacc9f666c4d8 [file] [log] [blame]
/*
* Copyright (C) 2015 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.backup;
import android.app.AppGlobals;
import android.app.backup.BackupDataInputStream;
import android.app.backup.BackupDataOutput;
import android.app.backup.BackupHelper;
import android.content.Context;
import android.content.pm.IPackageManager;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.Slog;
import android.util.Xml;
import com.android.internal.util.FastXmlSerializer;
import com.android.org.bouncycastle.util.Arrays;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class PreferredActivityBackupHelper implements BackupHelper {
private static final String TAG = "PreferredBackup";
private static final boolean DEBUG = true;
// current schema of the backup state blob
private static final int STATE_VERSION = 1;
// key under which the preferred-activity state blob is committed to backup
private static final String KEY_PREFERRED = "preferred-activity";
final Context mContext;
public PreferredActivityBackupHelper(Context context) {
mContext = context;
}
// The fds passed here are shared among all helpers, so we mustn't close them
private void writeState(ParcelFileDescriptor stateFile, byte[] payload) {
try {
FileOutputStream fos = new FileOutputStream(stateFile.getFileDescriptor());
// We explicitly don't close 'out' because we must not close the backing fd.
// The FileOutputStream will not close it implicitly.
@SuppressWarnings("resource")
DataOutputStream out = new DataOutputStream(fos);
out.writeInt(STATE_VERSION);
if (payload == null) {
out.writeInt(0);
} else {
out.writeInt(payload.length);
out.write(payload);
}
} catch (IOException e) {
Slog.e(TAG, "Unable to write updated state", e);
}
}
private byte[] readState(ParcelFileDescriptor oldStateFd) {
FileInputStream fis = new FileInputStream(oldStateFd.getFileDescriptor());
BufferedInputStream bis = new BufferedInputStream(fis);
@SuppressWarnings("resource")
DataInputStream in = new DataInputStream(bis);
byte[] oldState = null;
try {
int version = in.readInt();
if (version == STATE_VERSION) {
int size = in.readInt();
if (size > 0) {
if (size > 200*1024) {
Slog.w(TAG, "Suspiciously large state blog; ignoring. N=" + size);
} else {
// size looks okay; make the return buffer and fill it
oldState = new byte[size];
in.read(oldState);
}
}
} else {
Slog.w(TAG, "Prior state from unrecognized version " + version);
}
} catch (EOFException e) {
// Empty file is expected on first backup, so carry on. If the state
// is truncated we just treat it the same way.
oldState = null;
} catch (Exception e) {
Slog.w(TAG, "Error examing prior backup state " + e.getMessage());
oldState = null;
}
return oldState;
}
@Override
public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) {
byte[] payload = null;
try {
byte[] oldPayload = readState(oldState);
IPackageManager pm = AppGlobals.getPackageManager();
byte[] newPayload = pm.getPreferredActivityBackup(UserHandle.USER_OWNER);
if (!Arrays.areEqual(oldPayload, newPayload)) {
if (DEBUG) {
Slog.i(TAG, "State has changed => writing new preferred app payload");
}
data.writeEntityHeader(KEY_PREFERRED, newPayload.length);
data.writeEntityData(newPayload, newPayload.length);
} else {
if (DEBUG) {
Slog.i(TAG, "No change to state => not writing to wire");
}
}
// Always need to re-record the state, even if nothing changed
payload = newPayload;
} catch (Exception e) {
// On failures we'll wind up committing a zero-size state payload. This is
// a forward-safe situation because we know we commit the entire new payload
// on prior-state mismatch.
Slog.w(TAG, "Unable to record preferred activities", e);
} finally {
writeState(newState, payload);
}
}
@Override
public void restoreEntity(BackupDataInputStream data) {
IPackageManager pm = AppGlobals.getPackageManager();
try {
byte[] payload = new byte[data.size()];
data.read(payload);
if (DEBUG) {
Slog.i(TAG, "Restoring preferred activities; size=" + payload.length);
}
pm.restorePreferredActivities(payload, UserHandle.USER_OWNER);
} catch (Exception e) {
Slog.e(TAG, "Exception reading restore data", e);
}
}
@Override
public void writeNewStateDescription(ParcelFileDescriptor newState) {
writeState(newState, null);
}
}