blob: 6ac0d89c74d2734c35b3e51f34aaacc9f666c4d8 [file] [log] [blame]
Christopher Tatee012a232015-04-01 17:18:50 -07001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.backup;
18
19import android.app.AppGlobals;
20import android.app.backup.BackupDataInputStream;
21import android.app.backup.BackupDataOutput;
22import android.app.backup.BackupHelper;
23import android.content.Context;
24import android.content.pm.IPackageManager;
25import android.os.IBinder;
26import android.os.ParcelFileDescriptor;
27import android.os.ServiceManager;
28import android.os.UserHandle;
29import android.util.Slog;
30import android.util.Xml;
31
32import com.android.internal.util.FastXmlSerializer;
33import com.android.org.bouncycastle.util.Arrays;
34
35import org.xmlpull.v1.XmlPullParser;
36import org.xmlpull.v1.XmlSerializer;
37
38import java.io.BufferedInputStream;
39import java.io.ByteArrayInputStream;
40import java.io.ByteArrayOutputStream;
41import java.io.DataInputStream;
42import java.io.DataOutputStream;
43import java.io.EOFException;
44import java.io.FileInputStream;
45import java.io.FileOutputStream;
46import java.io.IOException;
47
48public class PreferredActivityBackupHelper implements BackupHelper {
49 private static final String TAG = "PreferredBackup";
50 private static final boolean DEBUG = true;
51
52 // current schema of the backup state blob
53 private static final int STATE_VERSION = 1;
54
55 // key under which the preferred-activity state blob is committed to backup
56 private static final String KEY_PREFERRED = "preferred-activity";
57
58 final Context mContext;
59
60 public PreferredActivityBackupHelper(Context context) {
61 mContext = context;
62 }
63
64 // The fds passed here are shared among all helpers, so we mustn't close them
65 private void writeState(ParcelFileDescriptor stateFile, byte[] payload) {
66 try {
67 FileOutputStream fos = new FileOutputStream(stateFile.getFileDescriptor());
68
69 // We explicitly don't close 'out' because we must not close the backing fd.
70 // The FileOutputStream will not close it implicitly.
71 @SuppressWarnings("resource")
72 DataOutputStream out = new DataOutputStream(fos);
73
74 out.writeInt(STATE_VERSION);
75 if (payload == null) {
76 out.writeInt(0);
77 } else {
78 out.writeInt(payload.length);
79 out.write(payload);
80 }
81 } catch (IOException e) {
82 Slog.e(TAG, "Unable to write updated state", e);
83 }
84 }
85
86 private byte[] readState(ParcelFileDescriptor oldStateFd) {
87 FileInputStream fis = new FileInputStream(oldStateFd.getFileDescriptor());
88 BufferedInputStream bis = new BufferedInputStream(fis);
89
90 @SuppressWarnings("resource")
91 DataInputStream in = new DataInputStream(bis);
92
93 byte[] oldState = null;
94 try {
95 int version = in.readInt();
96 if (version == STATE_VERSION) {
97 int size = in.readInt();
98 if (size > 0) {
99 if (size > 200*1024) {
100 Slog.w(TAG, "Suspiciously large state blog; ignoring. N=" + size);
101 } else {
102 // size looks okay; make the return buffer and fill it
103 oldState = new byte[size];
104 in.read(oldState);
105 }
106 }
107 } else {
108 Slog.w(TAG, "Prior state from unrecognized version " + version);
109 }
110 } catch (EOFException e) {
111 // Empty file is expected on first backup, so carry on. If the state
112 // is truncated we just treat it the same way.
113 oldState = null;
114 } catch (Exception e) {
115 Slog.w(TAG, "Error examing prior backup state " + e.getMessage());
116 oldState = null;
117 }
118
119 return oldState;
120 }
121
122 @Override
123 public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
124 ParcelFileDescriptor newState) {
125 byte[] payload = null;
126 try {
127 byte[] oldPayload = readState(oldState);
128
129 IPackageManager pm = AppGlobals.getPackageManager();
130 byte[] newPayload = pm.getPreferredActivityBackup(UserHandle.USER_OWNER);
131 if (!Arrays.areEqual(oldPayload, newPayload)) {
132 if (DEBUG) {
133 Slog.i(TAG, "State has changed => writing new preferred app payload");
134 }
135 data.writeEntityHeader(KEY_PREFERRED, newPayload.length);
136 data.writeEntityData(newPayload, newPayload.length);
137 } else {
138 if (DEBUG) {
139 Slog.i(TAG, "No change to state => not writing to wire");
140 }
141 }
142
143 // Always need to re-record the state, even if nothing changed
144 payload = newPayload;
145 } catch (Exception e) {
146 // On failures we'll wind up committing a zero-size state payload. This is
147 // a forward-safe situation because we know we commit the entire new payload
148 // on prior-state mismatch.
149 Slog.w(TAG, "Unable to record preferred activities", e);
150 } finally {
151 writeState(newState, payload);
152 }
153 }
154
155 @Override
156 public void restoreEntity(BackupDataInputStream data) {
157 IPackageManager pm = AppGlobals.getPackageManager();
158 try {
159 byte[] payload = new byte[data.size()];
160 data.read(payload);
161 if (DEBUG) {
162 Slog.i(TAG, "Restoring preferred activities; size=" + payload.length);
163 }
164 pm.restorePreferredActivities(payload, UserHandle.USER_OWNER);
165 } catch (Exception e) {
166 Slog.e(TAG, "Exception reading restore data", e);
167 }
168 }
169
170 @Override
171 public void writeNewStateDescription(ParcelFileDescriptor newState) {
172 writeState(newState, null);
173 }
174
175}