Single-user restrictions

Introduces a new "blocked" state for each package. This is used to temporarily
disable an app via Settings->Restrictions.

PIN creation and challenge activities for use by Settings and other apps. PIN
is stored by the User Manager and it manages the interval for retry attempts
across reboots.

Change-Id: I4915329d1f72399bbcaf93a9ca9c0d2e69d098dd
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 9308feb..524403c 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -1933,8 +1933,6 @@
                         getDataPathForPackage(packageName, 0).getPath();
                 pkg.applicationInfo.nativeLibraryDir = ps.nativeLibraryPathString;
             }
-            // pkg.mSetEnabled = ps.getEnabled(userId);
-            // pkg.mSetStopped = ps.getStopped(userId);
             return generatePackageInfo(pkg, flags, userId);
         }
         return null;
@@ -6149,6 +6147,120 @@
         mHandler.sendMessage(msg);
     }
 
+    private void sendPackageAddedForUser(String packageName, PackageSetting pkgSetting, int userId) {
+        Bundle extras = new Bundle(1);
+        extras.putInt(Intent.EXTRA_UID, UserHandle.getUid(userId, pkgSetting.appId));
+
+        sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
+                packageName, extras, null, null, new int[] {userId});
+        try {
+            IActivityManager am = ActivityManagerNative.getDefault();
+            final boolean isSystem =
+                    isSystemApp(pkgSetting) || isUpdatedSystemApp(pkgSetting);
+            if (isSystem && am.isUserRunning(userId, false)) {
+                // The just-installed/enabled app is bundled on the system, so presumed
+                // to be able to run automatically without needing an explicit launch.
+                // Send it a BOOT_COMPLETED if it would ordinarily have gotten one.
+                Intent bcIntent = new Intent(Intent.ACTION_BOOT_COMPLETED)
+                        .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
+                        .setPackage(packageName);
+                am.broadcastIntent(null, bcIntent, null, null, 0, null, null, null,
+                        android.app.AppOpsManager.OP_NONE, false, false, userId);
+            }
+        } catch (RemoteException e) {
+            // shouldn't happen
+            Slog.w(TAG, "Unable to bootstrap installed package", e);
+        }
+    }
+
+    @Override
+    public boolean setApplicationBlockedSettingAsUser(String packageName, boolean blocked,
+            int userId) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
+        PackageSetting pkgSetting;
+        final int uid = Binder.getCallingUid();
+        if (UserHandle.getUserId(uid) != userId) {
+            mContext.enforceCallingPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    "setApplicationBlocked for user " + userId);
+        }
+
+        if (blocked && isPackageDeviceAdmin(packageName, userId)) {
+            Slog.w(TAG, "Not blocking package " + packageName + ": has active device admin");
+            return false;
+        }
+
+        long callingId = Binder.clearCallingIdentity();
+        try {
+            boolean sendAdded = false;
+            boolean sendRemoved = false;
+            // writer
+            synchronized (mPackages) {
+                pkgSetting = mSettings.mPackages.get(packageName);
+                if (pkgSetting == null) {
+                    return false;
+                }
+                if (pkgSetting.getBlocked(userId) != blocked) {
+                    pkgSetting.setBlocked(blocked, userId);
+                    mSettings.writePackageRestrictionsLPr(userId);
+                    if (blocked) {
+                        sendRemoved = true;
+                    } else {
+                        sendAdded = true;
+                    }
+                }
+            }
+            if (sendAdded) {
+                sendPackageAddedForUser(packageName, pkgSetting, userId);
+                return true;
+            }
+            if (sendRemoved) {
+                sendPackageBlockedForUser(packageName, pkgSetting, userId);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+        return false;
+    }
+
+    private void sendPackageBlockedForUser(String packageName, PackageSetting pkgSetting,
+            int userId) {
+        final PackageRemovedInfo info = new PackageRemovedInfo();
+        info.removedPackage = packageName;
+        info.removedUsers = new int[] {userId};
+        info.uid = UserHandle.getUid(userId, pkgSetting.appId);
+        info.sendBroadcast(false, false, false);
+    }
+
+    /**
+     * Returns true if application is not found or there was an error. Otherwise it returns
+     * the blocked state of the package for the given user.
+     */
+    @Override
+    public boolean getApplicationBlockedSettingAsUser(String packageName, int userId) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
+        PackageSetting pkgSetting;
+        final int uid = Binder.getCallingUid();
+        if (UserHandle.getUserId(uid) != userId) {
+            mContext.enforceCallingPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    "getApplicationBlocked for user " + userId);
+        }
+        long callingId = Binder.clearCallingIdentity();
+        try {
+            // writer
+            synchronized (mPackages) {
+                pkgSetting = mSettings.mPackages.get(packageName);
+                if (pkgSetting == null) {
+                    return true;
+                }
+                return pkgSetting.getBlocked(userId);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+    }
+
     /**
      * @hide
      */
@@ -6180,33 +6292,14 @@
                 }
                 if (!pkgSetting.getInstalled(userId)) {
                     pkgSetting.setInstalled(true, userId);
+                    pkgSetting.setBlocked(false, userId);
                     mSettings.writePackageRestrictionsLPr(userId);
-                    extras.putInt(Intent.EXTRA_UID, UserHandle.getUid(userId, pkgSetting.appId));
                     sendAdded = true;
                 }
             }
 
             if (sendAdded) {
-                sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
-                        packageName, extras, null, null, new int[] {userId});
-                try {
-                    IActivityManager am = ActivityManagerNative.getDefault();
-                    final boolean isSystem =
-                            isSystemApp(pkgSetting) || isUpdatedSystemApp(pkgSetting);
-                    if (isSystem && am.isUserRunning(userId, false)) {
-                        // The just-installed/enabled app is bundled on the system, so presumed
-                        // to be able to run automatically without needing an explicit launch.
-                        // Send it a BOOT_COMPLETED if it would ordinarily have gotten one.
-                        Intent bcIntent = new Intent(Intent.ACTION_BOOT_COMPLETED)
-                                .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
-                                .setPackage(packageName);
-                        am.broadcastIntent(null, bcIntent, null, null, 0, null, null, null,
-                                android.app.AppOpsManager.OP_NONE, false, false, userId);
-                    }
-                } catch (RemoteException e) {
-                    // shouldn't happen
-                    Slog.w(TAG, "Unable to bootstrap installed package", e);
-                }
+                sendPackageAddedForUser(packageName, pkgSetting, userId);
             }
         } finally {
             Binder.restoreCallingIdentity(callingId);
@@ -8697,6 +8790,19 @@
         });
     }
 
+    private boolean isPackageDeviceAdmin(String packageName, int userId) {
+        IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface(
+                ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
+        try {
+            if (dpm != null && (dpm.packageHasActiveAdmins(packageName, userId)
+                    || dpm.isDeviceOwner(packageName))) {
+                return true;
+            }
+        } catch (RemoteException e) {
+        }
+        return false;
+    }
+
     /**
      *  This method is an internal method that could be get invoked either
      *  to delete an installed package or to clean up a failed installation.
@@ -8715,15 +8821,9 @@
         final PackageRemovedInfo info = new PackageRemovedInfo();
         final boolean res;
 
-        IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface(
-                ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
-        try {
-            if (dpm != null && (dpm.packageHasActiveAdmins(packageName, userId)
-                    || dpm.isDeviceOwner(packageName))) {
-                Slog.w(TAG, "Not removing package " + packageName + ": has active device admin");
-                return PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER;
-            }
-        } catch (RemoteException e) {
+        if (isPackageDeviceAdmin(packageName, userId)) {
+            Slog.w(TAG, "Not removing package " + packageName + ": has active device admin");
+            return PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER;
         }
 
         boolean removedForAllUsers = false;
@@ -9039,6 +9139,7 @@
                         false, //installed
                         true,  //stopped
                         true,  //notLaunched
+                        false, //blocked
                         null, null, null);
                 if (!isSystemApp(ps)) {
                     if (ps.isAnyInstalled(sUserManager.getUserIds())) {
diff --git a/services/java/com/android/server/pm/PackageSettingBase.java b/services/java/com/android/server/pm/PackageSettingBase.java
index b3fd60c..7747c8f 100644
--- a/services/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/java/com/android/server/pm/PackageSettingBase.java
@@ -260,14 +260,24 @@
         modifyUserState(userId).notLaunched = stop;
     }
 
+    boolean getBlocked(int userId) {
+        return readUserState(userId).blocked;
+    }
+
+    void setBlocked(boolean blocked, int userId) {
+        modifyUserState(userId).blocked = blocked;
+    }
+
     void setUserState(int userId, int enabled, boolean installed, boolean stopped,
-            boolean notLaunched, String lastDisableAppCaller, HashSet<String> enabledComponents,
+            boolean notLaunched, boolean blocked,
+            String lastDisableAppCaller, HashSet<String> enabledComponents,
             HashSet<String> disabledComponents) {
         PackageUserState state = modifyUserState(userId);
         state.enabled = enabled;
         state.installed = installed;
         state.stopped = stopped;
         state.notLaunched = notLaunched;
+        state.blocked = blocked;
         state.lastDisableAppCaller = lastDisableAppCaller;
         state.enabledComponents = enabledComponents;
         state.disabledComponents = disabledComponents;
diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java
index 0cae5d65..2d7d8a0 100644
--- a/services/java/com/android/server/pm/Settings.java
+++ b/services/java/com/android/server/pm/Settings.java
@@ -109,6 +109,7 @@
     private static final String ATTR_ENABLED = "enabled";
     private static final String ATTR_ENABLED_CALLER = "enabledCaller";
     private static final String ATTR_STOPPED = "stopped";
+    private static final String ATTR_BLOCKED = "blocked";
     private static final String ATTR_INSTALLED = "inst";
 
     private final File mSettingsFilename;
@@ -462,6 +463,7 @@
                                     installed,
                                     true, // stopped,
                                     true, // notLaunched
+                                    false, // blocked
                                     null, null, null);
                             writePackageRestrictionsLPr(user.id);
                         }
@@ -860,6 +862,7 @@
                                 true,   // installed
                                 false,  // stopped
                                 false,  // notLaunched
+                                false,  // blocked
                                 null, null, null);
                     }
                     return;
@@ -913,6 +916,9 @@
                     final String stoppedStr = parser.getAttributeValue(null, ATTR_STOPPED);
                     final boolean stopped = stoppedStr == null
                             ? false : Boolean.parseBoolean(stoppedStr);
+                    final String blockedStr = parser.getAttributeValue(null, ATTR_BLOCKED);
+                    final boolean blocked = blockedStr == null
+                            ? false : Boolean.parseBoolean(blockedStr);
                     final String notLaunchedStr = parser.getAttributeValue(null, ATTR_NOT_LAUNCHED);
                     final boolean notLaunched = stoppedStr == null
                             ? false : Boolean.parseBoolean(notLaunchedStr);
@@ -936,7 +942,7 @@
                         }
                     }
 
-                    ps.setUserState(userId, enabled, installed, stopped, notLaunched,
+                    ps.setUserState(userId, enabled, installed, stopped, notLaunched, blocked,
                             enabledCaller, enabledComponents, disabledComponents);
                 } else if (tagName.equals("preferred-activities")) {
                     readPreferredActivitiesLPw(parser, userId);
@@ -1044,6 +1050,7 @@
                 PackageUserState ustate = pkg.readUserState(userId);
                 if (ustate.stopped || ustate.notLaunched || !ustate.installed
                         || ustate.enabled != COMPONENT_ENABLED_STATE_DEFAULT
+                        || ustate.blocked
                         || (ustate.enabledComponents != null
                                 && ustate.enabledComponents.size() > 0)
                         || (ustate.disabledComponents != null
@@ -1061,6 +1068,9 @@
                     if (ustate.notLaunched) {
                         serializer.attribute(null, ATTR_NOT_LAUNCHED, "true");
                     }
+                    if (ustate.blocked) {
+                        serializer.attribute(null, ATTR_BLOCKED, "true");
+                    }
                     if (ustate.enabled != COMPONENT_ENABLED_STATE_DEFAULT) {
                         serializer.attribute(null, ATTR_ENABLED,
                                 Integer.toString(ustate.enabled));
@@ -2847,6 +2857,8 @@
             pw.print(prefix); pw.print("  User "); pw.print(user.id); pw.print(": ");
             pw.print(" installed=");
             pw.print(ps.getInstalled(user.id));
+            pw.print(" blocked=");
+            pw.print(ps.getBlocked(user.id));
             pw.print(" stopped=");
             pw.print(ps.getStopped(user.id));
             pw.print(" notLaunched=");
diff --git a/services/java/com/android/server/pm/UserManagerService.java b/services/java/com/android/server/pm/UserManagerService.java
index 1323c93..d86f2c7 100644
--- a/services/java/com/android/server/pm/UserManagerService.java
+++ b/services/java/com/android/server/pm/UserManagerService.java
@@ -39,12 +39,15 @@
 import android.os.IUserManager;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.AtomicFile;
+import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
+import android.util.SparseLongArray;
 import android.util.TimeUtils;
 import android.util.Xml;
 
@@ -63,6 +66,9 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -78,6 +84,10 @@
     private static final String ATTR_ID = "id";
     private static final String ATTR_CREATION_TIME = "created";
     private static final String ATTR_LAST_LOGGED_IN_TIME = "lastLoggedIn";
+    private static final String ATTR_SALT = "salt";
+    private static final String ATTR_PIN_HASH = "pinHash";
+    private static final String ATTR_FAILED_ATTEMPTS = "failedAttempts";
+    private static final String ATTR_LAST_RETRY_MS = "lastAttemptMs";
     private static final String ATTR_SERIAL_NO = "serialNumber";
     private static final String ATTR_NEXT_SERIAL_NO = "nextSerialNumber";
     private static final String ATTR_PARTIAL = "partial";
@@ -107,6 +117,13 @@
 
     private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms
 
+    // Number of attempts before jumping to the next BACKOFF_TIMES slot
+    private static final int BACKOFF_INC_INTERVAL = 5;
+
+    // Amount of time to force the user to wait before entering the PIN again, after failing
+    // BACKOFF_INC_INTERVAL times.
+    private static final int[] BACKOFF_TIMES = { 0, 30*1000, 60*1000, 5*60*1000, 30*60*1000 };
+
     private final Context mContext;
     private final PackageManagerService mPm;
     private final Object mInstallLock;
@@ -121,6 +138,16 @@
     private final SparseArray<UserInfo> mUsers = new SparseArray<UserInfo>();
     private final SparseArray<Bundle> mUserRestrictions = new SparseArray<Bundle>();
 
+    class RestrictionsPinState {
+        long salt;
+        String pinHash;
+        int failedAttempts;
+        long lastAttemptTime;
+    }
+
+    private final SparseArray<RestrictionsPinState> mRestrictionsPinStates =
+            new SparseArray<RestrictionsPinState>();
+
     /**
      * Set of user IDs being actively removed. Removed IDs linger in this set
      * for several seconds to work around a VFS caching issue.
@@ -604,6 +631,21 @@
             serializer.attribute(null, ATTR_CREATION_TIME, Long.toString(userInfo.creationTime));
             serializer.attribute(null, ATTR_LAST_LOGGED_IN_TIME,
                     Long.toString(userInfo.lastLoggedInTime));
+            RestrictionsPinState pinState = mRestrictionsPinStates.get(userInfo.id);
+            if (pinState != null) {
+                if (pinState.salt != 0) {
+                    serializer.attribute(null, ATTR_SALT, Long.toString(pinState.salt));
+                }
+                if (pinState.pinHash != null) {
+                    serializer.attribute(null, ATTR_PIN_HASH, pinState.pinHash);
+                }
+                if (pinState.failedAttempts != 0) {
+                    serializer.attribute(null, ATTR_FAILED_ATTEMPTS,
+                            Integer.toString(pinState.failedAttempts));
+                    serializer.attribute(null, ATTR_LAST_RETRY_MS,
+                            Long.toString(pinState.lastAttemptTime));
+                }
+            }
             if (userInfo.iconPath != null) {
                 serializer.attribute(null,  ATTR_ICON_PATH, userInfo.iconPath);
             }
@@ -690,6 +732,10 @@
         String iconPath = null;
         long creationTime = 0L;
         long lastLoggedInTime = 0L;
+        long salt = 0L;
+        String pinHash = null;
+        int failedAttempts = 0;
+        long lastAttemptTime = 0L;
         boolean partial = false;
         Bundle restrictions = new Bundle();
 
@@ -722,6 +768,10 @@
                 iconPath = parser.getAttributeValue(null, ATTR_ICON_PATH);
                 creationTime = readLongAttribute(parser, ATTR_CREATION_TIME, 0);
                 lastLoggedInTime = readLongAttribute(parser, ATTR_LAST_LOGGED_IN_TIME, 0);
+                salt = readLongAttribute(parser, ATTR_SALT, 0L);
+                pinHash = parser.getAttributeValue(null, ATTR_PIN_HASH);
+                failedAttempts = readIntAttribute(parser, ATTR_FAILED_ATTEMPTS, 0);
+                lastAttemptTime = readLongAttribute(parser, ATTR_LAST_RETRY_MS, 0L);
                 String valueString = parser.getAttributeValue(null, ATTR_PARTIAL);
                 if ("true".equals(valueString)) {
                     partial = true;
@@ -761,6 +811,17 @@
             userInfo.lastLoggedInTime = lastLoggedInTime;
             userInfo.partial = partial;
             mUserRestrictions.append(id, restrictions);
+            if (salt != 0L) {
+                RestrictionsPinState pinState = mRestrictionsPinStates.get(id);
+                if (pinState == null) {
+                    pinState = new RestrictionsPinState();
+                    mRestrictionsPinStates.put(id, pinState);
+                }
+                pinState.salt = salt;
+                pinState.pinHash = pinHash;
+                pinState.failedAttempts = failedAttempts;
+                pinState.lastAttemptTime = lastAttemptTime;
+            }
             return userInfo;
 
         } catch (IOException ioe) {
@@ -949,6 +1010,7 @@
             }
         }, MINUTE_IN_MILLIS);
 
+        mRestrictionsPinStates.remove(userHandle);
         // Remove user file
         AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + ".xml"));
         userFile.delete();
@@ -999,6 +1061,123 @@
         }
     }
 
+    @Override
+    public boolean changeRestrictionsPin(String newPin) {
+        checkManageUsersPermission("Only system can modify the restrictions pin");
+        int userId = UserHandle.getCallingUserId();
+        synchronized (mPackagesLock) {
+            RestrictionsPinState pinState = mRestrictionsPinStates.get(userId);
+            if (pinState == null) {
+                pinState = new RestrictionsPinState();
+            }
+            if (newPin == null) {
+                pinState.salt = 0;
+                pinState.pinHash = null;
+            } else {
+                try {
+                    pinState.salt = SecureRandom.getInstance("SHA1PRNG").nextLong();
+                } catch (NoSuchAlgorithmException e) {
+                    pinState.salt = (long) (Math.random() * Long.MAX_VALUE);
+                }
+                pinState.pinHash = passwordToHash(newPin, pinState.salt);
+                pinState.failedAttempts = 0;
+            }
+            mRestrictionsPinStates.put(userId, pinState);
+            writeUserLocked(mUsers.get(userId));
+        }
+        return true;
+    }
+
+    @Override
+    public int checkRestrictionsPin(String pin) {
+        checkManageUsersPermission("Only system can verify the restrictions pin");
+        int userId = UserHandle.getCallingUserId();
+        synchronized (mPackagesLock) {
+            RestrictionsPinState pinState = mRestrictionsPinStates.get(userId);
+            // If there's no pin set, return error code
+            if (pinState == null || pinState.salt == 0 || pinState.pinHash == null) {
+                return UserManager.PIN_VERIFICATION_FAILED_NOT_SET;
+            } else if (pin == null) {
+                // If just checking if user can be prompted, return remaining time
+                int waitTime = getRemainingTimeForPinAttempt(pinState);
+                Slog.d(LOG_TAG, "Remaining waittime peek=" + waitTime);
+                return waitTime;
+            } else {
+                int waitTime = getRemainingTimeForPinAttempt(pinState);
+                Slog.d(LOG_TAG, "Remaining waittime=" + waitTime);
+                if (waitTime > 0) {
+                    return waitTime;
+                }
+                if (passwordToHash(pin, pinState.salt).equals(pinState.pinHash)) {
+                    pinState.failedAttempts = 0;
+                    writeUserLocked(mUsers.get(userId));
+                    return UserManager.PIN_VERIFICATION_SUCCESS;
+                } else {
+                    pinState.failedAttempts++;
+                    pinState.lastAttemptTime = System.currentTimeMillis();
+                    writeUserLocked(mUsers.get(userId));
+                    return waitTime;
+                }
+            }
+        }
+    }
+
+    private int getRemainingTimeForPinAttempt(RestrictionsPinState pinState) {
+        int backoffIndex = Math.min(pinState.failedAttempts / BACKOFF_INC_INTERVAL,
+                BACKOFF_TIMES.length - 1);
+        int backoffTime = (pinState.failedAttempts % BACKOFF_INC_INTERVAL) == 0 ?
+                BACKOFF_TIMES[backoffIndex] : 0;
+        return (int) Math.max(backoffTime + pinState.lastAttemptTime - System.currentTimeMillis(),
+                0);
+    }
+
+    @Override
+    public boolean hasRestrictionsPin() {
+        int userId = UserHandle.getCallingUserId();
+        synchronized (mPackagesLock) {
+            RestrictionsPinState pinState = mRestrictionsPinStates.get(userId);
+            if (pinState == null || pinState.salt == 0 || pinState.pinHash == null) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /*
+     * Generate a hash for the given password. To avoid brute force attacks, we use a salted hash.
+     * Not the most secure, but it is at least a second level of protection. First level is that
+     * the file is in a location only readable by the system process.
+     * @param password the password.
+     * @param salt the randomly generated salt
+     * @return the hash of the pattern in a String.
+     */
+    private String passwordToHash(String password, long salt) {
+        if (password == null) {
+            return null;
+        }
+        String algo = null;
+        String hashed = salt + password;
+        try {
+            byte[] saltedPassword = (password + salt).getBytes();
+            byte[] sha1 = MessageDigest.getInstance(algo = "SHA-1").digest(saltedPassword);
+            byte[] md5 = MessageDigest.getInstance(algo = "MD5").digest(saltedPassword);
+            hashed = toHex(sha1) + toHex(md5);
+        } catch (NoSuchAlgorithmException e) {
+            Log.w(LOG_TAG, "Failed to encode string because of missing algorithm: " + algo);
+        }
+        return hashed;
+    }
+
+    private static String toHex(byte[] ary) {
+        final String hex = "0123456789ABCDEF";
+        String ret = "";
+        for (int i = 0; i < ary.length; i++) {
+            ret += hex.charAt((ary[i] >> 4) & 0xf);
+            ret += hex.charAt(ary[i] & 0xf);
+        }
+        return ret;
+    }
+
     private int getUidForPackage(String packageName) {
         long ident = Binder.clearCallingIdentity();
         try {