Service for reading and writing blocks to PST partition

Permits apps with permission
android.permission.ACCESS_PERSISTENT_PARTITION to obtain
a read and write data blocks to the PST partition.

Only one block ever exists at one time in PST. When
a client writes another block, the previous one is
overwritten.

This permits storing a block of data that will live
across factory resets.

Change-Id: I8f23df3531f3c0512118eb4b7530eff8a8e81c83
diff --git a/Android.mk b/Android.mk
index a7eb03d..5d957d0 100644
--- a/Android.mk
+++ b/Android.mk
@@ -210,6 +210,7 @@
 	core/java/android/service/dreams/IDozeHardware.aidl \
 	core/java/android/service/dreams/IDreamManager.aidl \
 	core/java/android/service/dreams/IDreamService.aidl \
+	core/java/android/service/persistentdata/IPersistentDataBlockService.aidl \
 	core/java/android/service/fingerprint/IFingerprintService.aidl \
 	core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl \
 	core/java/android/service/trust/ITrustAgentService.aidl \
diff --git a/api/current.txt b/api/current.txt
index e8e4e0c..80c4698 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -47723,7 +47723,6 @@
     ctor public Locale(java.lang.String, java.lang.String);
     ctor public Locale(java.lang.String, java.lang.String, java.lang.String);
     method public java.lang.Object clone();
-    method public static java.util.Locale forLanguageTag(java.lang.String);
     method public static java.util.Locale[] getAvailableLocales();
     method public java.lang.String getCountry();
     method public static java.util.Locale getDefault();
@@ -47733,24 +47732,15 @@
     method public java.lang.String getDisplayLanguage(java.util.Locale);
     method public final java.lang.String getDisplayName();
     method public java.lang.String getDisplayName(java.util.Locale);
-    method public java.lang.String getDisplayScript();
-    method public java.lang.String getDisplayScript(java.util.Locale);
     method public final java.lang.String getDisplayVariant();
     method public java.lang.String getDisplayVariant(java.util.Locale);
-    method public java.lang.String getExtension(char);
-    method public java.util.Set<java.lang.Character> getExtensionKeys();
     method public java.lang.String getISO3Country();
     method public java.lang.String getISO3Language();
     method public static java.lang.String[] getISOCountries();
     method public static java.lang.String[] getISOLanguages();
     method public java.lang.String getLanguage();
-    method public java.lang.String getScript();
-    method public java.util.Set<java.lang.String> getUnicodeLocaleAttributes();
-    method public java.util.Set<java.lang.String> getUnicodeLocaleKeys();
-    method public java.lang.String getUnicodeLocaleType(java.lang.String);
     method public java.lang.String getVariant();
     method public static synchronized void setDefault(java.util.Locale);
-    method public java.lang.String toLanguageTag();
     method public final java.lang.String toString();
     field public static final java.util.Locale CANADA;
     field public static final java.util.Locale CANADA_FRENCH;
@@ -47768,33 +47758,14 @@
     field public static final java.util.Locale KOREA;
     field public static final java.util.Locale KOREAN;
     field public static final java.util.Locale PRC;
-    field public static final char PRIVATE_USE_EXTENSION = 120; // 0x0078 'x'
     field public static final java.util.Locale ROOT;
     field public static final java.util.Locale SIMPLIFIED_CHINESE;
     field public static final java.util.Locale TAIWAN;
     field public static final java.util.Locale TRADITIONAL_CHINESE;
     field public static final java.util.Locale UK;
-    field public static final char UNICODE_LOCALE_EXTENSION = 117; // 0x0075 'u'
     field public static final java.util.Locale US;
   }
 
-  public static final class Locale.Builder {
-    ctor public Locale.Builder();
-    method public java.util.Locale.Builder addUnicodeLocaleAttribute(java.lang.String);
-    method public java.util.Locale build();
-    method public java.util.Locale.Builder clear();
-    method public java.util.Locale.Builder clearExtensions();
-    method public java.util.Locale.Builder removeUnicodeLocaleAttribute(java.lang.String);
-    method public java.util.Locale.Builder setExtension(char, java.lang.String);
-    method public java.util.Locale.Builder setLanguage(java.lang.String);
-    method public java.util.Locale.Builder setLanguageTag(java.lang.String);
-    method public java.util.Locale.Builder setLocale(java.util.Locale);
-    method public java.util.Locale.Builder setRegion(java.lang.String);
-    method public java.util.Locale.Builder setScript(java.lang.String);
-    method public java.util.Locale.Builder setUnicodeLocaleKeyword(java.lang.String, java.lang.String);
-    method public java.util.Locale.Builder setVariant(java.lang.String);
-  }
-
   public abstract interface Map {
     method public abstract void clear();
     method public abstract boolean containsKey(java.lang.Object);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index fa2266b..cacb2df 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -20,6 +20,8 @@
 import android.net.wifi.WifiScanner;
 import android.os.Build;
 
+import android.service.persistentdata.IPersistentDataBlockService;
+import android.service.persistentdata.PersistentDataBlockManager;
 import com.android.internal.policy.PolicyManager;
 import com.android.internal.util.Preconditions;
 
@@ -733,7 +735,14 @@
             public Object createService(ContextImpl ctx) {
                 IBinder b = ServiceManager.getService(JOB_SCHEDULER_SERVICE);
                 return new JobSchedulerImpl(IJobScheduler.Stub.asInterface(b));
-            }});
+        }});
+
+        registerService(PERSISTENT_DATA_BLOCK_SERVICE, new ServiceFetcher() {
+            public Object createService(ContextImpl ctx) {
+                IBinder b = ServiceManager.getService(PERSISTENT_DATA_BLOCK_SERVICE);
+                return new PersistentDataBlockManager(
+                        IPersistentDataBlockService.Stub.asInterface(b));
+        }});
     }
 
     static ContextImpl getImpl(Context context) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 80b6b58..04275d8 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2801,6 +2801,16 @@
     public static final String JOB_SCHEDULER_SERVICE = "jobscheduler";
 
     /**
+     * Use with {@link #getSystemService} to retrieve a {@link
+     * android.service.persistentdata.PersistentDataBlockManager} instance retrieving
+     * a file descriptor for a persistent data block.
+     * @see #getSystemService
+     * @see android.service.persistentdata.PersistentDataBlockManager
+     * @hide
+     */
+    public static final String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
diff --git a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
new file mode 100644
index 0000000..06e776d
--- /dev/null
+++ b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
@@ -0,0 +1,20 @@
+package android.service.persistentdata;
+
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Internal interface through which to communicate to the
+ * PersistentDataBlockService. The persistent data block allows writing
+ * raw data and setting the OEM unlock enabled/disabled bit contained
+ * in the partition.
+ *
+ * @hide
+ */
+interface IPersistentDataBlockService {
+    int write(in byte[] data);
+    int read(out byte[] data);
+    int getDataBlockSize();
+
+    void setOemUnlockEnabled(boolean enabled);
+    boolean getOemUnlockEnabled();
+}
diff --git a/core/java/android/service/persistentdata/PersistentDataBlockManager.java b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
new file mode 100644
index 0000000..afcf717
--- /dev/null
+++ b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
@@ -0,0 +1,97 @@
+package android.service.persistentdata;
+
+import android.os.RemoteException;
+import android.util.Slog;
+
+/**
+ * Interface for reading and writing data blocks to a persistent partition.
+ *
+ * Allows writing one block at a time. Namely, each time
+ * {@link android.service.persistentdata.PersistentDataBlockManager}.write(byte[] data)
+ * is called, it will overwite the data that was previously written on the block.
+ *
+ * Clients can query the size of the currently written block via
+ * {@link android.service.persistentdata.PersistentDataBlockManager}.getTotalDataSize().
+ *
+ * Clients can any number of bytes from the currently written block up to its total size by invoking
+ * {@link android.service.persistentdata.PersistentDataBlockManager}.read(byte[] data).
+ *
+ * @hide
+ */
+public class PersistentDataBlockManager {
+    private static final String TAG = PersistentDataBlockManager.class.getSimpleName();
+    private IPersistentDataBlockService sService;
+
+    public PersistentDataBlockManager(IPersistentDataBlockService service) {
+        sService = service;
+    }
+
+    /**
+     * Writes {@code data} to the persistent partition. Previously written data
+     * will be overwritten. This data will persist across factory resets.
+     *
+     * @param data the data to write
+     */
+    public void write(byte[] data) {
+        try {
+            sService.write(data);
+        } catch (RemoteException e) {
+            onError("writing data");
+        }
+    }
+
+    /**
+     * Tries to read {@code data.length} bytes into {@code data}. Call {@code getDataBlockSize()}
+     * to determine the total size of the block currently residing in the persistent partition.
+     *
+     * @param data the buffer in which to read the data
+     * @return the actual number of bytes read
+     */
+    public int read(byte[] data) {
+        try {
+            return sService.read(data);
+        } catch (RemoteException e) {
+            onError("reading data");
+            return -1;
+        }
+    }
+
+    /**
+     * Retrieves the size of the block currently written to the persistent partition.
+     */
+    public int getDataBlockSize() {
+        try {
+            return sService.getDataBlockSize();
+        } catch (RemoteException e) {
+            onError("getting data block size");
+            return 0;
+        }
+    }
+
+    /**
+     * Writes a byte enabling or disabling the ability to "OEM unlock" the device.
+     */
+    public void setOemUnlockEnabled(boolean enabled) {
+        try {
+            sService.setOemUnlockEnabled(enabled);
+        } catch (RemoteException e) {
+            onError("setting OEM unlock enabled to " + enabled);
+        }
+    }
+
+    /**
+     * Returns whether or not "OEM unlock" is enabled or disabled on this device.
+     */
+    public boolean getOemUnlockEnabled() {
+        try {
+            return sService.getOemUnlockEnabled();
+        } catch (RemoteException e) {
+            onError("getting OEM unlock enabled bit");
+            return false;
+        }
+    }
+
+    private void onError(String msg) {
+        Slog.v(TAG, "Remote exception while " + msg);
+    }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 59399d2..e38fce9 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1075,6 +1075,13 @@
     <permission android:name="android.permission.TV_INPUT_HARDWARE"
         android:protectionLevel="signatureOrSystem" />
 
+    <!-- @hide Allows enabling/disabling OEM unlock
+   <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.OEM_UNLOCK_STATE"
+                android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+                android:protectionLevel="signature" />
+
+
     <!-- =========================================== -->
     <!-- Permissions associated with audio capture -->
     <!-- =========================================== -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f34fbc13..4585b78 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1610,12 +1610,15 @@
         -->
     </string-array>
 
-<!-- Flag indicating apps will skip sending hold request before merge. In this case
-         IMS service implementation will do both.i.e.hold followed by merge. -->
+    <!-- Flag indicating which package name can access the persistent data partition -->
+    <string name="config_persistentDataPackageName" translatable="false"></string>
+
+    <!-- Flag indicating apps will skip sending hold request before merge. In this case
+        IMS service implementation will do both.i.e.hold followed by merge. -->
     <bool name="skipHoldBeforeMerge">true</bool>
 
     <!-- Flag indicating emergency calls will always use IMS irrespective of the state of
-         the IMS connection -->
+    the IMS connection -->
     <bool name="useImsAlwaysForEmergencyCall">true</bool>
 
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9422212..2065088 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1668,6 +1668,7 @@
   <java-symbol type="string" name="config_customAdbPublicKeyConfirmationComponent" />
   <java-symbol type="string" name="config_customVpnConfirmDialogComponent" />
   <java-symbol type="string" name="config_defaultNetworkScorerPackageName" />
+  <java-symbol type="string" name="config_persistentDataPackageName" />
 
   <java-symbol type="layout" name="resolver_list" />
   <java-symbol type="id" name="resolver_list" />
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
new file mode 100644
index 0000000..1eded6f
--- /dev/null
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -0,0 +1,261 @@
+package com.android.server;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.service.persistentdata.IPersistentDataBlockService;
+import android.util.Log;
+import com.android.internal.R;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+
+/**
+ * Service for reading and writing blocks to a persistent partition.
+ * This data will live across factory resets.
+ *
+ * Allows writing one block at a time. Namely, each time
+ * {@link android.service.persistentdata.IPersistentDataBlockService}.write(byte[] data)
+ * is called, it will overwite the data that was previously written on the block.
+ *
+ * Clients can query the size of the currently written block via
+ * {@link android.service.persistentdata.IPersistentDataBlockService}.getTotalDataSize().
+ *
+ * Clients can any number of bytes from the currently written block up to its total size by invoking
+ * {@link android.service.persistentdata.IPersistentDataBlockService}.read(byte[] data)
+ */
+public class PersistentDataBlockService extends SystemService {
+    private static final String TAG = PersistentDataBlockService.class.getSimpleName();
+
+    private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
+    private static final int HEADER_SIZE = 8;
+    private static final int BLOCK_ID = 0x1990;
+
+    private final Context mContext;
+    private final String mDataBlockFile;
+    private long mBlockDeviceSize;
+
+    private final int mAllowedUid;
+
+    public PersistentDataBlockService(Context context) {
+        super(context);
+        mContext = context;
+        mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP);
+        mBlockDeviceSize = 0; // Load lazily
+        String allowedPackage = context.getResources()
+                .getString(R.string.config_persistentDataPackageName);
+        PackageManager pm = mContext.getPackageManager();
+        int allowedUid = -1;
+        try {
+            allowedUid = pm.getPackageUid(allowedPackage,
+                    Binder.getCallingUserHandle().getIdentifier());
+        } catch (PackageManager.NameNotFoundException e) {
+            // not expected
+            Log.e(TAG, "not able to find package " + allowedPackage, e);
+        }
+
+        mAllowedUid = allowedUid;
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService);
+    }
+
+    private void enforceOemUnlockPermission() {
+        mContext.enforceCallingOrSelfPermission(
+                Manifest.permission.OEM_UNLOCK_STATE,
+                "Can't access OEM unlock state");
+    }
+
+    private void enforceUid(int callingUid) {
+        if (callingUid != mAllowedUid) {
+            throw new SecurityException("uid " + callingUid + " not allowed to access PST");
+        }
+    }
+
+    private int getTotalDataSize(DataInputStream inputStream) throws IOException {
+        int totalDataSize;
+        int blockId = inputStream.readInt();
+        if (blockId == BLOCK_ID) {
+            totalDataSize = inputStream.readInt();
+        } else {
+            totalDataSize = 0;
+        }
+        return totalDataSize;
+    }
+
+    private long maybeReadBlockDeviceSize() {
+        synchronized (this) {
+            if (mBlockDeviceSize == 0) {
+                mBlockDeviceSize = getBlockDeviceSize(mDataBlockFile);
+            }
+        }
+
+        return mBlockDeviceSize;
+    }
+
+    private native long getBlockDeviceSize(String path);
+
+    private final IBinder mService = new IPersistentDataBlockService.Stub() {
+        @Override
+        public int write(byte[] data) throws RemoteException {
+            enforceUid(Binder.getCallingUid());
+
+            // Need to ensure we don't write over the last byte
+            if (data.length > maybeReadBlockDeviceSize() - HEADER_SIZE - 1) {
+                return -1;
+            }
+
+            DataOutputStream outputStream;
+            try {
+                outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
+            } catch (FileNotFoundException e) {
+                Log.e(TAG, "partition not available?", e);
+                return -1;
+            }
+
+            ByteBuffer headerAndData = ByteBuffer.allocate(data.length + HEADER_SIZE);
+            headerAndData.putInt(BLOCK_ID);
+            headerAndData.putInt(data.length);
+            headerAndData.put(data);
+
+            try {
+                outputStream.write(headerAndData.array());
+                return data.length;
+            } catch (IOException e) {
+                Log.e(TAG, "failed writing to the persistent data block", e);
+                return -1;
+            } finally {
+                try {
+                    outputStream.close();
+                } catch (IOException e) {
+                    Log.e(TAG, "failed closing output stream", e);
+                }
+            }
+        }
+
+        @Override
+        public int read(byte[] data) {
+            enforceUid(Binder.getCallingUid());
+
+            DataInputStream inputStream;
+            try {
+                inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
+            } catch (FileNotFoundException e) {
+                Log.e(TAG, "partition not available?", e);
+                return -1;
+            }
+
+            try {
+                int totalDataSize = getTotalDataSize(inputStream);
+                return inputStream.read(data, 0,
+                        (data.length > totalDataSize) ? totalDataSize : data.length);
+            } catch (IOException e) {
+                Log.e(TAG, "failed to read data", e);
+                return -1;
+            } finally {
+                try {
+                    inputStream.close();
+                } catch (IOException e) {
+                    Log.e(TAG, "failed to close OutputStream");
+                }
+            }
+        }
+
+        @Override
+        public void setOemUnlockEnabled(boolean enabled) {
+            enforceOemUnlockPermission();
+            FileOutputStream outputStream;
+            try {
+                outputStream = new FileOutputStream(new File(mDataBlockFile));
+            } catch (FileNotFoundException e) {
+                Log.e(TAG, "parition not available", e);
+                return;
+            }
+
+            try {
+                FileChannel channel = outputStream.getChannel();
+
+                channel.position(maybeReadBlockDeviceSize() - 1);
+
+                ByteBuffer data = ByteBuffer.allocate(1);
+                data.put(enabled ? (byte) 1 : (byte) 0);
+                data.flip();
+
+                channel.write(data);
+            } catch (IOException e) {
+                Log.e(TAG, "unable to access persistent partition", e);
+            } finally {
+                try {
+                    outputStream.close();
+                } catch (IOException e) {
+                    Log.e(TAG, "failed to close OutputStream");
+                }
+            }
+        }
+
+        @Override
+        public boolean getOemUnlockEnabled() {
+            enforceOemUnlockPermission();
+            DataInputStream inputStream;
+            try {
+                inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
+            } catch (FileNotFoundException e) {
+                Log.e(TAG, "partition not available");
+                return false;
+            }
+
+            try {
+                inputStream.skip(maybeReadBlockDeviceSize() - 1);
+                return inputStream.readByte() != 0;
+            } catch (IOException e) {
+                Log.e(TAG, "unable to access persistent partition", e);
+                return false;
+            } finally {
+                try {
+                    inputStream.close();
+                } catch (IOException e) {
+                    Log.e(TAG, "failed to close OutputStream");
+                }
+            }
+        }
+
+        @Override
+        public int getDataBlockSize() {
+            enforceUid(Binder.getCallingUid());
+
+            DataInputStream inputStream;
+            try {
+                inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
+            } catch (FileNotFoundException e) {
+                Log.e(TAG, "partition not available");
+                return 0;
+            }
+
+            try {
+                return getTotalDataSize(inputStream);
+            } catch (IOException e) {
+                Log.e(TAG, "error reading data block size");
+                return 0;
+            } finally {
+                try {
+                    inputStream.close();
+                } catch (IOException e) {
+                    Log.e(TAG, "failed to close OutputStream");
+                }
+            }
+        }
+    };
+}
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index db44d3a..5599760 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -26,6 +26,7 @@
     $(LOCAL_REL_DIR)/com_android_server_UsbDeviceManager.cpp \
     $(LOCAL_REL_DIR)/com_android_server_UsbHostManager.cpp \
     $(LOCAL_REL_DIR)/com_android_server_VibratorService.cpp \
+    $(LOCAL_REL_DIR)/com_android_server_PersistentDataBlockService.cpp \
     $(LOCAL_REL_DIR)/onload.cpp
 
 include external/stlport/libstlport.mk
diff --git a/services/core/jni/com_android_server_PersistentDataBlockService.cpp b/services/core/jni/com_android_server_PersistentDataBlockService.cpp
new file mode 100644
index 0000000..673a6e2
--- /dev/null
+++ b/services/core/jni/com_android_server_PersistentDataBlockService.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#include <android_runtime/AndroidRuntime.h>
+#include <JNIHelp.h>
+#include <jni.h>
+
+#include <utils/misc.h>
+#include <sys/ioctl.h>
+#include <sys/mount.h>
+
+#include <inttypes.h>
+#include <fcntl.h>
+
+namespace android {
+
+    uint64_t get_block_device_size(int fd)
+    {
+        uint64_t size = 0;
+        int ret;
+
+        ret = ioctl(fd, BLKGETSIZE64, &size);
+
+        if (ret)
+            return 0;
+
+        return size;
+    }
+
+    static jlong com_android_server_PeristentDataBlockService_getBlockDeviceSize(JNIEnv *env, jclass, jstring jpath) {
+        const char *path = env->GetStringUTFChars(jpath, 0);
+        int fd = open(path, O_RDONLY);
+
+        if (fd < 0)
+            return 0;
+
+        return get_block_device_size(fd);
+    }
+
+    static JNINativeMethod sMethods[] = {
+         /* name, signature, funcPtr */
+        {"getBlockDeviceSize", "(Ljava/lang/String;)J", (void*)com_android_server_PeristentDataBlockService_getBlockDeviceSize},
+    };
+
+    int register_android_server_PersistentDataBlockService(JNIEnv* env)
+    {
+        return jniRegisterNativeMethods(env, "com/android/server/PersistentDataBlockService",
+                                        sMethods, NELEM(sMethods));
+    }
+
+} /* namespace android */
\ No newline at end of file
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index a302104..bf7501e 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -41,6 +41,7 @@
 int register_android_server_hdmi_HdmiCecController(JNIEnv* env);
 int register_android_server_hdmi_HdmiMhlController(JNIEnv* env);
 int register_android_server_tv_TvInputHal(JNIEnv* env);
+int register_android_server_PersistentDataBlockService(JNIEnv* env);
 };
 
 using namespace android;
@@ -77,6 +78,7 @@
     register_android_server_hdmi_HdmiCecController(env);
     register_android_server_hdmi_HdmiMhlController(env);
     register_android_server_tv_TvInputHal(env);
+    register_android_server_PersistentDataBlockService(env);
 
     return JNI_VERSION_1_4;
 }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b63bbe7..e0ebd54 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -135,6 +135,7 @@
             "com.android.server.ethernet.EthernetService";
     private static final String JOB_SCHEDULER_SERVICE_CLASS =
             "com.android.server.job.JobSchedulerService";
+    private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
 
     private final int mFactoryTestMode;
     private Timer mProfilerSnapshotTimer;
@@ -574,6 +575,10 @@
                     reportWtf("starting LockSettingsService service", e);
                 }
 
+                if (!SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP).equals("")) {
+                    mSystemServiceManager.startService(PersistentDataBlockService.class);
+                }
+
                 // Always start the Device Policy Manager, so that the API is compatible with
                 // API8.
                 mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class);