blob: 2085f3277648e210ec60341c59a0cdbd4b8aa00b [file] [log] [blame]
Andres Morales963295e2014-07-10 15:40:24 -07001/*
2 * Copyright (C) 2014 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
Andres Morales68d4acd2014-07-01 19:40:41 -070017package com.android.server;
18
19import android.Manifest;
Guang Zhu514c5802014-09-12 15:14:00 -070020import android.app.ActivityManager;
Andres Morales68d4acd2014-07-01 19:40:41 -070021import android.content.Context;
22import android.content.pm.PackageManager;
23import android.os.Binder;
24import android.os.IBinder;
25import android.os.RemoteException;
26import android.os.SystemProperties;
Andres Morales6429f312014-08-04 16:35:15 -070027import android.os.UserHandle;
Xiaohui Chenf0660782015-09-02 14:25:19 -070028import android.os.UserManager;
Andres Morales68d4acd2014-07-01 19:40:41 -070029import android.service.persistentdata.IPersistentDataBlockService;
Andres Morales74e9b182016-02-22 12:33:33 -080030import android.service.persistentdata.PersistentDataBlockManager;
Andres Morales963295e2014-07-10 15:40:24 -070031import android.util.Slog;
Guang Zhu514c5802014-09-12 15:14:00 -070032
Andres Morales68d4acd2014-07-01 19:40:41 -070033import com.android.internal.R;
Guang Zhu514c5802014-09-12 15:14:00 -070034
Andres Morales963295e2014-07-10 15:40:24 -070035import libcore.io.IoUtils;
Andres Morales68d4acd2014-07-01 19:40:41 -070036
37import java.io.DataInputStream;
38import java.io.DataOutputStream;
39import java.io.File;
40import java.io.FileInputStream;
41import java.io.FileNotFoundException;
42import java.io.FileOutputStream;
43import java.io.IOException;
44import java.nio.ByteBuffer;
45import java.nio.channels.FileChannel;
Andres Morales28301302014-11-12 07:56:46 -080046import java.security.MessageDigest;
47import java.security.NoSuchAlgorithmException;
48import java.util.Arrays;
Andres Morales68d4acd2014-07-01 19:40:41 -070049
50/**
51 * Service for reading and writing blocks to a persistent partition.
Andres Morales963295e2014-07-10 15:40:24 -070052 * This data will live across factory resets not initiated via the Settings UI.
53 * When a device is factory reset through Settings this data is wiped.
Andres Morales68d4acd2014-07-01 19:40:41 -070054 *
55 * Allows writing one block at a time. Namely, each time
56 * {@link android.service.persistentdata.IPersistentDataBlockService}.write(byte[] data)
57 * is called, it will overwite the data that was previously written on the block.
58 *
59 * Clients can query the size of the currently written block via
60 * {@link android.service.persistentdata.IPersistentDataBlockService}.getTotalDataSize().
61 *
62 * Clients can any number of bytes from the currently written block up to its total size by invoking
63 * {@link android.service.persistentdata.IPersistentDataBlockService}.read(byte[] data)
64 */
65public class PersistentDataBlockService extends SystemService {
66 private static final String TAG = PersistentDataBlockService.class.getSimpleName();
67
68 private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
69 private static final int HEADER_SIZE = 8;
Andres Morales963295e2014-07-10 15:40:24 -070070 // Magic number to mark block device as adhering to the format consumed by this service
Andres Morales28301302014-11-12 07:56:46 -080071 private static final int PARTITION_TYPE_MARKER = 0x19901873;
Andres Morales963295e2014-07-10 15:40:24 -070072 // Limit to 100k as blocks larger than this might cause strain on Binder.
Andres Morales963295e2014-07-10 15:40:24 -070073 private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
Andres Morales28301302014-11-12 07:56:46 -080074 public static final int DIGEST_SIZE_BYTES = 32;
Andres Morales5ca4cc52015-03-19 16:37:54 -070075 private static final String OEM_UNLOCK_PROP = "sys.oem_unlock_allowed";
Andres Morales74e9b182016-02-22 12:33:33 -080076 private static final String FLASH_LOCK_PROP = "ro.boot.flash.locked";
77 private static final String FLASH_LOCK_LOCKED = "1";
78 private static final String FLASH_LOCK_UNLOCKED = "0";
Andres Morales68d4acd2014-07-01 19:40:41 -070079
80 private final Context mContext;
81 private final String mDataBlockFile;
Andres Morales963295e2014-07-10 15:40:24 -070082 private final Object mLock = new Object();
Andres Morales6429f312014-08-04 16:35:15 -070083
Andres Moralesa31c23d2014-10-30 15:31:31 -070084 private int mAllowedUid = -1;
Andres Morales963295e2014-07-10 15:40:24 -070085 private long mBlockDeviceSize;
Andres Morales68d4acd2014-07-01 19:40:41 -070086
87 public PersistentDataBlockService(Context context) {
88 super(context);
89 mContext = context;
90 mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP);
Andres Morales963295e2014-07-10 15:40:24 -070091 mBlockDeviceSize = -1; // Load lazily
Xiaohui Chenf0660782015-09-02 14:25:19 -070092 mAllowedUid = getAllowedUid(UserHandle.USER_SYSTEM);
Andres Morales6429f312014-08-04 16:35:15 -070093 }
94
Andres Moralesa31c23d2014-10-30 15:31:31 -070095 private int getAllowedUid(int userHandle) {
Andres Morales6429f312014-08-04 16:35:15 -070096 String allowedPackage = mContext.getResources()
Andres Morales68d4acd2014-07-01 19:40:41 -070097 .getString(R.string.config_persistentDataPackageName);
98 PackageManager pm = mContext.getPackageManager();
99 int allowedUid = -1;
100 try {
Jeff Sharkeyc5967e92016-01-07 18:50:29 -0700101 allowedUid = pm.getPackageUidAsUser(allowedPackage,
102 PackageManager.MATCH_SYSTEM_ONLY, userHandle);
Andres Morales68d4acd2014-07-01 19:40:41 -0700103 } catch (PackageManager.NameNotFoundException e) {
104 // not expected
Andres Morales963295e2014-07-10 15:40:24 -0700105 Slog.e(TAG, "not able to find package " + allowedPackage, e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700106 }
Andres Moralesa31c23d2014-10-30 15:31:31 -0700107 return allowedUid;
Andres Morales68d4acd2014-07-01 19:40:41 -0700108 }
109
110 @Override
111 public void onStart() {
Andres Morales28301302014-11-12 07:56:46 -0800112 enforceChecksumValidity();
Andres Morales1ce7d172015-01-07 14:24:57 -0800113 formatIfOemUnlockEnabled();
Andres Morales68d4acd2014-07-01 19:40:41 -0700114 publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService);
115 }
116
Andres Morales1ce7d172015-01-07 14:24:57 -0800117 private void formatIfOemUnlockEnabled() {
Andres Morales5ca4cc52015-03-19 16:37:54 -0700118 boolean enabled = doGetOemUnlockEnabled();
119 if (enabled) {
Andres Morales1ce7d172015-01-07 14:24:57 -0800120 synchronized (mLock) {
Andres Moralesc8f952c2015-03-19 08:34:55 -0700121 formatPartitionLocked(true);
Andres Morales1ce7d172015-01-07 14:24:57 -0800122 }
123 }
Andres Morales5ca4cc52015-03-19 16:37:54 -0700124
125 SystemProperties.set(OEM_UNLOCK_PROP, enabled ? "1" : "0");
Andres Morales1ce7d172015-01-07 14:24:57 -0800126 }
127
Andres Morales68d4acd2014-07-01 19:40:41 -0700128 private void enforceOemUnlockPermission() {
129 mContext.enforceCallingOrSelfPermission(
130 Manifest.permission.OEM_UNLOCK_STATE,
131 "Can't access OEM unlock state");
132 }
133
134 private void enforceUid(int callingUid) {
Andres Moralesa31c23d2014-10-30 15:31:31 -0700135 if (callingUid != mAllowedUid) {
Andres Morales68d4acd2014-07-01 19:40:41 -0700136 throw new SecurityException("uid " + callingUid + " not allowed to access PST");
137 }
138 }
139
Xiaohui Chenf0660782015-09-02 14:25:19 -0700140 private void enforceIsAdmin() {
141 final int userId = UserHandle.getCallingUserId();
142 final boolean isAdmin = UserManager.get(mContext).isUserAdmin(userId);
143 if (!isAdmin) {
144 throw new SecurityException(
145 "Only the Admin user is allowed to change OEM unlock state");
Andres Moralesa31c23d2014-10-30 15:31:31 -0700146 }
147 }
Andres Morales963295e2014-07-10 15:40:24 -0700148 private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException {
Andres Morales28301302014-11-12 07:56:46 -0800149 // skip over checksum
150 inputStream.skipBytes(DIGEST_SIZE_BYTES);
151
Andres Morales68d4acd2014-07-01 19:40:41 -0700152 int totalDataSize;
153 int blockId = inputStream.readInt();
Andres Morales963295e2014-07-10 15:40:24 -0700154 if (blockId == PARTITION_TYPE_MARKER) {
Andres Morales68d4acd2014-07-01 19:40:41 -0700155 totalDataSize = inputStream.readInt();
156 } else {
157 totalDataSize = 0;
158 }
159 return totalDataSize;
160 }
161
Andres Morales963295e2014-07-10 15:40:24 -0700162 private long getBlockDeviceSize() {
163 synchronized (mLock) {
164 if (mBlockDeviceSize == -1) {
165 mBlockDeviceSize = nativeGetBlockDeviceSize(mDataBlockFile);
Andres Morales68d4acd2014-07-01 19:40:41 -0700166 }
167 }
168
169 return mBlockDeviceSize;
170 }
171
Andres Morales28301302014-11-12 07:56:46 -0800172 private boolean enforceChecksumValidity() {
173 byte[] storedDigest = new byte[DIGEST_SIZE_BYTES];
174
175 synchronized (mLock) {
176 byte[] digest = computeDigestLocked(storedDigest);
177 if (digest == null || !Arrays.equals(storedDigest, digest)) {
178 Slog.i(TAG, "Formatting FRP partition...");
Andres Moralesc8f952c2015-03-19 08:34:55 -0700179 formatPartitionLocked(false);
Andres Morales28301302014-11-12 07:56:46 -0800180 return false;
181 }
182 }
183
184 return true;
185 }
186
187 private boolean computeAndWriteDigestLocked() {
188 byte[] digest = computeDigestLocked(null);
189 if (digest != null) {
190 DataOutputStream outputStream;
191 try {
192 outputStream = new DataOutputStream(
193 new FileOutputStream(new File(mDataBlockFile)));
194 } catch (FileNotFoundException e) {
195 Slog.e(TAG, "partition not available?", e);
196 return false;
197 }
198
199 try {
200 outputStream.write(digest, 0, DIGEST_SIZE_BYTES);
201 outputStream.flush();
202 } catch (IOException e) {
203 Slog.e(TAG, "failed to write block checksum", e);
204 return false;
205 } finally {
206 IoUtils.closeQuietly(outputStream);
207 }
208 return true;
209 } else {
210 return false;
211 }
212 }
213
214 private byte[] computeDigestLocked(byte[] storedDigest) {
215 DataInputStream inputStream;
216 try {
217 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
218 } catch (FileNotFoundException e) {
219 Slog.e(TAG, "partition not available?", e);
220 return null;
221 }
222
223 MessageDigest md;
224 try {
225 md = MessageDigest.getInstance("SHA-256");
226 } catch (NoSuchAlgorithmException e) {
227 // won't ever happen -- every implementation is required to support SHA-256
228 Slog.e(TAG, "SHA-256 not supported?", e);
229 IoUtils.closeQuietly(inputStream);
230 return null;
231 }
232
233 try {
234 if (storedDigest != null && storedDigest.length == DIGEST_SIZE_BYTES) {
235 inputStream.read(storedDigest);
236 } else {
237 inputStream.skipBytes(DIGEST_SIZE_BYTES);
238 }
239
240 int read;
241 byte[] data = new byte[1024];
242 md.update(data, 0, DIGEST_SIZE_BYTES); // include 0 checksum in digest
243 while ((read = inputStream.read(data)) != -1) {
244 md.update(data, 0, read);
245 }
246 } catch (IOException e) {
247 Slog.e(TAG, "failed to read partition", e);
248 return null;
249 } finally {
250 IoUtils.closeQuietly(inputStream);
251 }
252
253 return md.digest();
254 }
255
Andres Moralesc8f952c2015-03-19 08:34:55 -0700256 private void formatPartitionLocked(boolean setOemUnlockEnabled) {
Andres Morales28301302014-11-12 07:56:46 -0800257 DataOutputStream outputStream;
258 try {
259 outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
260 } catch (FileNotFoundException e) {
261 Slog.e(TAG, "partition not available?", e);
262 return;
263 }
264
265 byte[] data = new byte[DIGEST_SIZE_BYTES];
266 try {
267 outputStream.write(data, 0, DIGEST_SIZE_BYTES);
268 outputStream.writeInt(PARTITION_TYPE_MARKER);
269 outputStream.writeInt(0); // data size
270 outputStream.flush();
271 } catch (IOException e) {
272 Slog.e(TAG, "failed to format block", e);
273 return;
274 } finally {
275 IoUtils.closeQuietly(outputStream);
276 }
277
Andres Moralesc8f952c2015-03-19 08:34:55 -0700278 doSetOemUnlockEnabledLocked(setOemUnlockEnabled);
Andres Morales28301302014-11-12 07:56:46 -0800279 computeAndWriteDigestLocked();
280 }
281
282 private void doSetOemUnlockEnabledLocked(boolean enabled) {
283 FileOutputStream outputStream;
284 try {
285 outputStream = new FileOutputStream(new File(mDataBlockFile));
286 } catch (FileNotFoundException e) {
287 Slog.e(TAG, "partition not available", e);
288 return;
289 }
290
291 try {
292 FileChannel channel = outputStream.getChannel();
293
294 channel.position(getBlockDeviceSize() - 1);
295
296 ByteBuffer data = ByteBuffer.allocate(1);
297 data.put(enabled ? (byte) 1 : (byte) 0);
298 data.flip();
299 channel.write(data);
300 outputStream.flush();
301 } catch (IOException e) {
302 Slog.e(TAG, "unable to access persistent partition", e);
303 return;
304 } finally {
Andres Morales5ca4cc52015-03-19 16:37:54 -0700305 SystemProperties.set(OEM_UNLOCK_PROP, enabled ? "1" : "0");
Andres Morales28301302014-11-12 07:56:46 -0800306 IoUtils.closeQuietly(outputStream);
307 }
308 }
309
Andres Morales1ce7d172015-01-07 14:24:57 -0800310 private boolean doGetOemUnlockEnabled() {
311 DataInputStream inputStream;
312 try {
313 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
314 } catch (FileNotFoundException e) {
315 Slog.e(TAG, "partition not available");
316 return false;
317 }
318
319 try {
320 synchronized (mLock) {
321 inputStream.skip(getBlockDeviceSize() - 1);
322 return inputStream.readByte() != 0;
323 }
324 } catch (IOException e) {
325 Slog.e(TAG, "unable to access persistent partition", e);
326 return false;
327 } finally {
328 IoUtils.closeQuietly(inputStream);
329 }
330 }
331
Andres Morales963295e2014-07-10 15:40:24 -0700332 private native long nativeGetBlockDeviceSize(String path);
333 private native int nativeWipe(String path);
Andres Morales68d4acd2014-07-01 19:40:41 -0700334
335 private final IBinder mService = new IPersistentDataBlockService.Stub() {
336 @Override
337 public int write(byte[] data) throws RemoteException {
338 enforceUid(Binder.getCallingUid());
339
340 // Need to ensure we don't write over the last byte
Andres Morales963295e2014-07-10 15:40:24 -0700341 long maxBlockSize = getBlockDeviceSize() - HEADER_SIZE - 1;
342 if (data.length > maxBlockSize) {
343 // partition is ~500k so shouldn't be a problem to downcast
344 return (int) -maxBlockSize;
Andres Morales68d4acd2014-07-01 19:40:41 -0700345 }
346
347 DataOutputStream outputStream;
348 try {
349 outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
350 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700351 Slog.e(TAG, "partition not available?", e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700352 return -1;
353 }
354
355 ByteBuffer headerAndData = ByteBuffer.allocate(data.length + HEADER_SIZE);
Andres Morales963295e2014-07-10 15:40:24 -0700356 headerAndData.putInt(PARTITION_TYPE_MARKER);
Andres Morales68d4acd2014-07-01 19:40:41 -0700357 headerAndData.putInt(data.length);
358 headerAndData.put(data);
359
Andres Morales28301302014-11-12 07:56:46 -0800360 synchronized (mLock) {
Andres Morales68d4acd2014-07-01 19:40:41 -0700361 try {
Andres Morales28301302014-11-12 07:56:46 -0800362 byte[] checksum = new byte[DIGEST_SIZE_BYTES];
363 outputStream.write(checksum, 0, DIGEST_SIZE_BYTES);
364 outputStream.write(headerAndData.array());
365 outputStream.flush();
Andres Morales68d4acd2014-07-01 19:40:41 -0700366 } catch (IOException e) {
Andres Morales28301302014-11-12 07:56:46 -0800367 Slog.e(TAG, "failed writing to the persistent data block", e);
368 return -1;
369 } finally {
370 IoUtils.closeQuietly(outputStream);
371 }
372
373 if (computeAndWriteDigestLocked()) {
374 return data.length;
375 } else {
376 return -1;
Andres Morales68d4acd2014-07-01 19:40:41 -0700377 }
378 }
379 }
380
381 @Override
Andres Morales963295e2014-07-10 15:40:24 -0700382 public byte[] read() {
Andres Morales68d4acd2014-07-01 19:40:41 -0700383 enforceUid(Binder.getCallingUid());
Andres Morales28301302014-11-12 07:56:46 -0800384 if (!enforceChecksumValidity()) {
385 return new byte[0];
386 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700387
388 DataInputStream inputStream;
389 try {
390 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
391 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700392 Slog.e(TAG, "partition not available?", e);
393 return null;
Andres Morales68d4acd2014-07-01 19:40:41 -0700394 }
395
396 try {
Andres Morales963295e2014-07-10 15:40:24 -0700397 synchronized (mLock) {
398 int totalDataSize = getTotalDataSizeLocked(inputStream);
399
400 if (totalDataSize == 0) {
401 return new byte[0];
402 }
403
404 byte[] data = new byte[totalDataSize];
405 int read = inputStream.read(data, 0, totalDataSize);
406 if (read < totalDataSize) {
407 // something went wrong, not returning potentially corrupt data
408 Slog.e(TAG, "failed to read entire data block. bytes read: " +
409 read + "/" + totalDataSize);
410 return null;
411 }
412 return data;
413 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700414 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700415 Slog.e(TAG, "failed to read data", e);
416 return null;
Andres Morales68d4acd2014-07-01 19:40:41 -0700417 } finally {
418 try {
419 inputStream.close();
420 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700421 Slog.e(TAG, "failed to close OutputStream");
422 }
423 }
424 }
425
426 @Override
427 public void wipe() {
428 enforceOemUnlockPermission();
429
430 synchronized (mLock) {
431 int ret = nativeWipe(mDataBlockFile);
432
433 if (ret < 0) {
434 Slog.e(TAG, "failed to wipe persistent partition");
Andres Morales68d4acd2014-07-01 19:40:41 -0700435 }
436 }
437 }
438
439 @Override
440 public void setOemUnlockEnabled(boolean enabled) {
Guang Zhu514c5802014-09-12 15:14:00 -0700441 // do not allow monkey to flip the flag
442 if (ActivityManager.isUserAMonkey()) {
443 return;
444 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700445 enforceOemUnlockPermission();
Xiaohui Chenf0660782015-09-02 14:25:19 -0700446 enforceIsAdmin();
Andres Morales68d4acd2014-07-01 19:40:41 -0700447
Andres Morales28301302014-11-12 07:56:46 -0800448 synchronized (mLock) {
449 doSetOemUnlockEnabledLocked(enabled);
450 computeAndWriteDigestLocked();
Andres Morales68d4acd2014-07-01 19:40:41 -0700451 }
452 }
453
454 @Override
455 public boolean getOemUnlockEnabled() {
456 enforceOemUnlockPermission();
Andres Morales1ce7d172015-01-07 14:24:57 -0800457 return doGetOemUnlockEnabled();
Andres Morales68d4acd2014-07-01 19:40:41 -0700458 }
459
460 @Override
Andres Morales74e9b182016-02-22 12:33:33 -0800461 public int getFlashLockState() {
462 enforceOemUnlockPermission();
463 String locked = SystemProperties.get(FLASH_LOCK_PROP);
464 switch (locked) {
465 case FLASH_LOCK_LOCKED:
466 return PersistentDataBlockManager.FLASH_LOCK_LOCKED;
467 case FLASH_LOCK_UNLOCKED:
468 return PersistentDataBlockManager.FLASH_LOCK_UNLOCKED;
469 default:
470 return PersistentDataBlockManager.FLASH_LOCK_UNKNOWN;
471 }
472 }
473
474 @Override
Andres Morales68d4acd2014-07-01 19:40:41 -0700475 public int getDataBlockSize() {
Craig Lafayette66445a62015-03-27 09:01:43 -0400476 enforcePersistentDataBlockAccess();
Andres Morales68d4acd2014-07-01 19:40:41 -0700477
478 DataInputStream inputStream;
479 try {
480 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
481 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700482 Slog.e(TAG, "partition not available");
Andres Morales68d4acd2014-07-01 19:40:41 -0700483 return 0;
484 }
485
486 try {
Andres Morales963295e2014-07-10 15:40:24 -0700487 synchronized (mLock) {
488 return getTotalDataSizeLocked(inputStream);
489 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700490 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700491 Slog.e(TAG, "error reading data block size");
Andres Morales68d4acd2014-07-01 19:40:41 -0700492 return 0;
493 } finally {
Andres Morales963295e2014-07-10 15:40:24 -0700494 IoUtils.closeQuietly(inputStream);
Andres Morales68d4acd2014-07-01 19:40:41 -0700495 }
496 }
Andres Morales963295e2014-07-10 15:40:24 -0700497
Craig Lafayette66445a62015-03-27 09:01:43 -0400498 private void enforcePersistentDataBlockAccess() {
499 if (mContext.checkCallingPermission(Manifest.permission.ACCESS_PDB_STATE)
500 != PackageManager.PERMISSION_GRANTED) {
501 enforceUid(Binder.getCallingUid());
502 }
503 }
504
Andres Morales963295e2014-07-10 15:40:24 -0700505 @Override
506 public long getMaximumDataBlockSize() {
507 long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1;
508 return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
509 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700510 };
511}