OSDN Git Service

Have UserManagerService clear the restrictions and unblock apps
authorAmith Yamasani <yamasani@google.com>
Tue, 2 Jul 2013 18:17:30 +0000 (11:17 -0700)
committerAmith Yamasani <yamasani@google.com>
Thu, 18 Jul 2013 23:12:27 +0000 (16:12 -0700)
Since this is an operation that could take a few seconds to run and needs to be
completed even if Settings dies, best to do it in the user manager.

Refactored PIN challenge/setup UI with a field to verify existing pin
when changing to a new one.

Change-Id: I0b7df5b2ccb7f343aa9282a9245d3bc2b577a794

core/java/android/os/IUserManager.aidl
core/java/android/os/UserManager.java
core/java/com/android/internal/app/RestrictionsPinActivity.java
core/java/com/android/internal/app/RestrictionsPinSetupActivity.java
core/res/res/layout/restrictions_pin_challenge.xml [new file with mode: 0644]
core/res/res/layout/restrictions_pin_setup.xml [moved from core/res/res/layout/pin_challenge.xml with 76% similarity]
core/res/res/values/strings.xml
core/res/res/values/symbols.xml
services/java/com/android/server/pm/PackageManagerService.java
services/java/com/android/server/pm/UserManagerService.java

index 7589a5a..bd2d9ac 100644 (file)
@@ -49,4 +49,5 @@ interface IUserManager {
     boolean changeRestrictionsPin(in String newPin);
     int checkRestrictionsPin(in String pin);
     boolean hasRestrictionsPin();
+    void removeRestrictions();
 }
index c33a28a..cdaa868 100644 (file)
@@ -678,4 +678,13 @@ public class UserManager {
         }
         return false;
     }
+
+    /** @hide */
+    public void removeRestrictions() {
+        try {
+            mService.removeRestrictions();
+        } catch (RemoteException re) {
+            Log.w(TAG, "Could not change restrictions pin");
+        }
+    }
 }
index 57436f7..f8ce108 100644 (file)
@@ -26,6 +26,7 @@ import android.text.TextWatcher;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.WindowManager;
 import android.widget.EditText;
 import android.widget.TextView;
 import android.widget.TextView.OnEditorActionListener;
@@ -39,17 +40,24 @@ import com.android.internal.R;
 public class RestrictionsPinActivity extends AlertActivity
         implements DialogInterface.OnClickListener, TextWatcher, OnEditorActionListener {
 
-    private UserManager mUserManager;
+    protected UserManager mUserManager;
+    protected boolean mHasRestrictionsPin;
 
-    private EditText mPin1Text;
-    private EditText mPin2Text;
-    private TextView mPinErrorMessage;
-    private TextView mPinMessage;
+    protected EditText mPinText;
+    protected TextView mPinErrorMessage;
+    protected TextView mPinMessage;
 
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
+        mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
+        mHasRestrictionsPin = mUserManager.hasRestrictionsPin();
+        initUi();
+        setupAlert();
+    }
+
+    protected void initUi() {
         AlertController.AlertParams ap = mAlertParams;
         ap.mTitle = getString(R.string.restr_pin_enter_pin);
         ap.mPositiveButtonText = getString(R.string.ok);
@@ -58,18 +66,12 @@ public class RestrictionsPinActivity extends AlertActivity
         ap.mNegativeButtonListener = this;
         LayoutInflater inflater =
                 (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        ap.mView = inflater.inflate(R.layout.pin_challenge, null);
+        ap.mView = inflater.inflate(R.layout.restrictions_pin_challenge, null);
 
         mPinMessage = (TextView) ap.mView.findViewById(R.id.pin_message);
-        mPin1Text = (EditText) ap.mView.findViewById(R.id.pin1_text);
-        mPin2Text = (EditText) ap.mView.findViewById(R.id.pin2_text);
+        mPinText = (EditText) ap.mView.findViewById(R.id.pin_text);
         mPinErrorMessage = (TextView) ap.mView.findViewById(R.id.pin_error_message);
-        mPin1Text.addTextChangedListener(this);
-        mPin2Text.addTextChangedListener(this);
-
-        mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
-
-        setupAlert();
+        mPinText.addTextChangedListener(this);
     }
 
     protected boolean verifyingPin() {
@@ -81,19 +83,12 @@ public class RestrictionsPinActivity extends AlertActivity
 
         setPositiveButtonState(false);
         boolean hasPin = mUserManager.hasRestrictionsPin();
-        if (verifyingPin()) {
-            if (hasPin) {
-                mPinMessage.setVisibility(View.GONE);
-                mPinErrorMessage.setVisibility(View.GONE);
-                mPin2Text.setVisibility(View.GONE);
-                mPin1Text.setOnEditorActionListener(this);
-                updatePinTimer(-1);
-            } else {
-                setResult(RESULT_OK);
-                finish();
-            }
-        } else if (hasPin) {
-            // Shouldn't really be in this state, exit
+        if (hasPin) {
+            mPinMessage.setVisibility(View.GONE);
+            mPinErrorMessage.setVisibility(View.GONE);
+            mPinText.setOnEditorActionListener(this);
+            updatePinTimer(-1);
+        } else if (verifyingPin()) {
             setResult(RESULT_OK);
             finish();
         }
@@ -114,14 +109,14 @@ public class RestrictionsPinActivity extends AlertActivity
                     seconds);
             mPinErrorMessage.setText(String.format(formatString, seconds));
             mPinErrorMessage.setVisibility(View.VISIBLE);
-            mPin1Text.setEnabled(false);
-            mPin1Text.setText("");
+            mPinText.setEnabled(false);
+            mPinText.setText("");
             setPositiveButtonState(false);
-            mPin1Text.postDelayed(mCountdownRunnable, Math.min(1000, pinTimerMs));
+            mPinText.postDelayed(mCountdownRunnable, Math.min(1000, pinTimerMs));
         } else {
             mPinErrorMessage.setVisibility(View.INVISIBLE);
-            mPin1Text.setEnabled(true);
-            mPin1Text.setText("");
+            mPinText.setEnabled(true);
+            mPinText.setText("");
         }
     }
 
@@ -134,20 +129,13 @@ public class RestrictionsPinActivity extends AlertActivity
         }
     }
 
-    private void performPositiveButtonAction() {
-        if (verifyingPin()) {
-            int result = mUserManager.checkRestrictionsPin(mPin1Text.getText().toString());
-            if (result == UserManager.PIN_VERIFICATION_SUCCESS) {
-                setResult(RESULT_OK);
-                finish();
-            } else if (result >= 0) {
-                updatePinTimer(result);
-            }
-        } else {
-            if (mUserManager.changeRestrictionsPin(mPin1Text.getText().toString())) {
-                setResult(RESULT_OK);
-                finish();
-            }
+    protected void performPositiveButtonAction() {
+        int result = mUserManager.checkRestrictionsPin(mPinText.getText().toString());
+        if (result == UserManager.PIN_VERIFICATION_SUCCESS) {
+            setResult(RESULT_OK);
+            finish();
+        } else if (result >= 0) {
+            updatePinTimer(result);
         }
     }
 
@@ -157,16 +145,8 @@ public class RestrictionsPinActivity extends AlertActivity
 
     @Override
     public void onTextChanged(CharSequence s, int start, int before, int count) {
-        CharSequence pin1 = mPin1Text.getText();
-        if (!verifyingPin()) {
-            CharSequence pin2 = mPin2Text.getText();
-            boolean match = pin1 != null && pin2 != null && pin1.length() >= 4
-                    && pin1.toString().equals(pin2.toString());
-            setPositiveButtonState(match);
-            mPinErrorMessage.setVisibility(match ? View.INVISIBLE : View.VISIBLE);
-        } else {
-            setPositiveButtonState(pin1 != null && pin1.length() >= 4);
-        }
+        CharSequence pin = mPinText.getText();
+        setPositiveButtonState(pin != null && pin.length() >= 4);
     }
 
     @Override
index 35f2967..1d09292 100644 (file)
 
 package com.android.internal.app;
 
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.UserManager;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
 /**
  * This activity is launched by Settings and other apps to either create a new PIN or
- * challenge for an existing PIN. The PIN is maintained by UserManager.
+ * change an existing PIN. The PIN is maintained by UserManager.
  */
 public class RestrictionsPinSetupActivity extends RestrictionsPinActivity {
 
+    private EditText mNewPinText;
+    private EditText mConfirmPinText;
+
+    protected void initUi() {
+        AlertController.AlertParams ap = mAlertParams;
+        ap.mTitle = getString(R.string.restr_pin_enter_pin);
+        ap.mPositiveButtonText = getString(R.string.ok);
+        ap.mNegativeButtonText = getString(R.string.cancel);
+        ap.mPositiveButtonListener = this;
+        ap.mNegativeButtonListener = this;
+        LayoutInflater inflater =
+                (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        ap.mView = inflater.inflate(R.layout.restrictions_pin_setup, null);
+
+        mPinText = (EditText) ap.mView.findViewById(R.id.pin_text);
+        mPinMessage = (TextView) ap.mView.findViewById(R.id.pin_message);
+        mNewPinText = (EditText) ap.mView.findViewById(R.id.pin_new_text);
+        mConfirmPinText = (EditText) ap.mView.findViewById(R.id.pin_confirm_text);
+        mPinErrorMessage = (TextView) ap.mView.findViewById(R.id.pin_error_message);
+        mNewPinText.addTextChangedListener(this);
+        mConfirmPinText.addTextChangedListener(this);
+
+        if (!mHasRestrictionsPin) {
+            mPinText.setVisibility(View.GONE);
+        }
+    }
+
+    public void onResume() {
+        super.onResume();
+        setPositiveButtonState(false);
+    }
+
     protected boolean verifyingPin() {
         return false;
     }
+
+    private void setPositiveButtonState(boolean enabled) {
+        mAlert.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(enabled);
+    }
+
+    public void onClick(DialogInterface dialog, int which) {
+        setResult(RESULT_CANCELED);
+        if (which == AlertDialog.BUTTON_POSITIVE) {
+            performPositiveButtonAction();
+        } else if (which == AlertDialog.BUTTON_NEGATIVE) {
+            finish();
+        }
+    }
+
+    protected void performPositiveButtonAction() {
+        if (mHasRestrictionsPin) {
+            int result = mUserManager.checkRestrictionsPin(mPinText.getText().toString());
+            if (result != UserManager.PIN_VERIFICATION_SUCCESS) {
+                // TODO: Set message that existing pin doesn't match
+                return;
+            }
+        }
+        if (mUserManager.changeRestrictionsPin(mNewPinText.getText().toString())) {
+            // TODO: Send message to PIN recovery agent about the recovery email address
+            setResult(RESULT_OK);
+            finish();
+        }
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+        CharSequence pin = mPinText.getText();
+        CharSequence pin1 = mNewPinText.getText();
+        CharSequence pin2 = mConfirmPinText.getText();
+        boolean match = pin1 != null && pin2 != null && pin1.length() >= 4
+                && pin1.toString().equals(pin2.toString())
+                && (!mHasRestrictionsPin || (pin != null && pin.length() >= 4));
+        boolean showError = !TextUtils.isEmpty(pin1) && !TextUtils.isEmpty(pin2);
+        // TODO: Check recovery email address as well
+        setPositiveButtonState(match);
+        mPinErrorMessage.setVisibility((match || !showError) ? View.INVISIBLE : View.VISIBLE);
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+    }
+
+    @Override
+    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+        performPositiveButtonAction();
+        return true;
+    }
 }
diff --git a/core/res/res/layout/restrictions_pin_challenge.xml b/core/res/res/layout/restrictions_pin_challenge.xml
new file mode 100644 (file)
index 0000000..954af92
--- /dev/null
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+
+<!-- Layout used as the dialog's content View for EditTextPreference. -->
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginTop="48dp"
+    android:layout_marginBottom="48dp"
+    android:overScrollMode="ifContentScrolls">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="8dip"
+        android:orientation="vertical">
+
+        <TextView android:id="@+id/pin_message"
+            style="?android:attr/textAppearanceMedium"
+            android:layout_marginTop="16dp"
+            android:layout_marginBottom="16dp"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/restr_pin_create_pin"
+            android:textColor="?android:attr/textColorSecondary" />
+
+        <EditText android:id="@+id/pin_text"
+            style="?android:attr/textAppearanceMedium"
+            android:layout_marginBottom="16dp"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:hint="@string/restr_pin_enter_pin"
+            android:inputType="numberPassword"
+            android:textColor="?android:attr/textColorPrimary" />
+
+        <TextView android:id="@+id/pin_error_message"
+            style="?android:attr/textAppearanceSmall"
+            android:layout_marginBottom="16dp"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/restr_pin_error_doesnt_match"
+            android:textColor="#FFFF0000" />
+
+    </LinearLayout>
+
+</ScrollView>
similarity index 76%
rename from core/res/res/layout/pin_challenge.xml
rename to core/res/res/layout/restrictions_pin_setup.xml
index 2cb14b4..03ed696 100644 (file)
             android:text="@string/restr_pin_create_pin"
             android:textColor="?android:attr/textColorSecondary" />
 
-        <!-- TextView android:id="@+id/pin1_label"
-            style="?android:attr/textAppearanceSmall"
-            android:layout_marginBottom="16dp"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:text="@string/restr_pin_enter_pin"
-            android:textColor="?android:attr/textColorSecondary" /-->
-
-        <EditText android:id="@+id/pin1_text"
+        <EditText android:id="@+id/pin_text"
             style="?android:attr/textAppearanceMedium"
             android:layout_marginBottom="16dp"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:hint="@string/restr_pin_enter_pin"
-            android:inputType="textPassword"
+            android:hint="@string/restr_pin_enter_old_pin"
+            android:inputType="numberPassword"
             android:textColor="?android:attr/textColorPrimary" />
 
-        <!-- TextView android:id="@+id/pin2_label"
-            style="?android:attr/textAppearanceSmall"
+        <EditText android:id="@+id/pin_new_text"
+            style="?android:attr/textAppearanceMedium"
             android:layout_marginBottom="16dp"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:text="@string/restr_pin_confirm_pin"
-            android:textColor="?android:attr/textColorSecondary" /-->
+            android:hint="@string/restr_pin_enter_new_pin"
+            android:inputType="numberPassword"
+            android:textColor="?android:attr/textColorPrimary" />
 
-        <EditText android:id="@+id/pin2_text"
+        <EditText android:id="@+id/pin_confirm_text"
             style="?android:attr/textAppearanceMedium"
             android:layout_marginBottom="16dp"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:hint="@string/restr_pin_confirm_pin"
-            android:inputType="textPassword"
+            android:inputType="numberPassword"
             android:textColor="?android:attr/textColorPrimary" />
 
         <TextView android:id="@+id/pin_error_message"
index eba3f42..b01b50e 100644 (file)
     <!-- Write fail reason: couldn't write the printed content. [CHAR LIMIT=none] -->
     <string name="write_fail_reason_cannot_write">Error writing content</string>
 
+    <!-- PIN entry dialog label/hint for PIN [CHAR LIMIT=none] -->
+    <string name="restr_pin_enter_pin">Enter PIN</string>
+    <!-- PIN entry dialog label/hint for old PIN [CHAR LIMIT=none] -->
+    <string name="restr_pin_enter_old_pin">Current PIN</string>
+    <!-- PIN entry dialog label for new PIN [CHAR LIMIT=none] -->
+    <string name="restr_pin_enter_new_pin">New PIN</string>
+    <!-- PIN entry dialog label for new PIN confirmation [CHAR LIMIT=none] -->
+    <string name="restr_pin_confirm_pin">Confirm new PIN</string>
     <!-- PIN creation dialog message [CHAR LIMIT=none] -->
     <string name="restr_pin_create_pin">Create a PIN for modifying restrictions</string>
-    <!-- PIN entry dialog label for PIN [CHAR LIMIT=none] -->
-    <string name="restr_pin_enter_pin">Enter PIN</string>
-    <!-- PIN entry dialog label for PIN confirmation [CHAR LIMIT=none] -->
-    <string name="restr_pin_confirm_pin">Confirm PIN</string>
     <!-- PIN entry dialog error when PINs are not the same [CHAR LIMIT=none] -->
     <string name="restr_pin_error_doesnt_match">PINs don\'t match. Try again.</string>
     <!-- PIN entry dialog error when PIN is too short [CHAR LIMIT=none] -->
index e29e82b..8a12ac8 100755 (executable)
   <java-symbol type="id" name="breadcrumb_section" />
   <java-symbol type="id" name="action_bar_spinner" />
   <java-symbol type="id" name="pin_message" />
-  <java-symbol type="id" name="pin1_text" />
-  <java-symbol type="id" name="pin2_text" />
+  <java-symbol type="id" name="pin_text" />
+  <java-symbol type="id" name="pin_new_text" />
+  <java-symbol type="id" name="pin_confirm_text" />
   <java-symbol type="id" name="pin_error_message" />
 
   <java-symbol type="attr" name="actionModeShareDrawable" />
   <java-symbol type="layout" name="sms_short_code_confirmation_dialog" />
   <java-symbol type="layout" name="action_bar_up_container" />
   <java-symbol type="layout" name="app_not_authorized" />
-  <java-symbol type="layout" name="pin_challenge" />
+  <java-symbol type="layout" name="restrictions_pin_challenge" />
+  <java-symbol type="layout" name="restrictions_pin_setup" />
 
   <java-symbol type="anim" name="slide_in_child_bottom" />
   <java-symbol type="anim" name="slide_in_right" />
index 6f57261..7a01219 100755 (executable)
@@ -6189,9 +6189,9 @@ public class PackageManagerService extends IPackageManager.Stub {
         PackageSetting pkgSetting;
         final int uid = Binder.getCallingUid();
         if (UserHandle.getUserId(uid) != userId) {
-            mContext.enforceCallingPermission(
+            mContext.enforceCallingOrSelfPermission(
                     android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
-                    "setApplicationBlocked for user " + userId);
+                    "setApplicationBlockedSetting for user " + userId);
         }
 
         if (blocked && isPackageDeviceAdmin(packageName, userId)) {
@@ -6224,6 +6224,8 @@ public class PackageManagerService extends IPackageManager.Stub {
                 return true;
             }
             if (sendRemoved) {
+                killApplication(packageName, UserHandle.getUid(userId, pkgSetting.appId),
+                        "blocking pkg");
                 sendPackageBlockedForUser(packageName, pkgSetting, userId);
             }
         } finally {
@@ -10016,6 +10018,7 @@ public class PackageManagerService extends IPackageManager.Stub {
                 }
             }
         }
+        sUserManager.systemReady();
     }
 
     public boolean isSafeMode() {
index 2901212..16c2fe7 100644 (file)
@@ -21,11 +21,13 @@ import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
+import android.app.ActivityThread;
 import android.app.IStopUserCallback;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.RestrictionEntry;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.UserInfo;
@@ -51,6 +53,7 @@ import android.util.SparseLongArray;
 import android.util.TimeUtils;
 import android.util.Xml;
 
+import com.android.internal.content.PackageMonitor;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
 
@@ -229,6 +232,13 @@ public class UserManagerService extends IUserManager.Stub {
                 sInstance = this;
             }
         }
+
+    }
+
+    void systemReady() {
+        mUserPackageMonitor.register(ActivityThread.systemMain().getSystemContext(),
+                null, UserHandle.ALL, false);
+        userForeground(UserHandle.USER_OWNER);
     }
 
     @Override
@@ -822,11 +832,6 @@ public class UserManagerService extends IUserManager.Stub {
                 pinState.failedAttempts = failedAttempts;
                 pinState.lastAttemptTime = lastAttemptTime;
             }
-            // If this is not a restricted profile and there is no restrictions pin, clean up
-            // any restrictions files that might have been left behind.
-            if (!userInfo.isRestricted() && salt == 0) {
-                cleanAppRestrictions(id);
-            }
             return userInfo;
 
         } catch (IOException ioe) {
@@ -878,11 +883,22 @@ public class UserManagerService extends IUserManager.Stub {
         }
     }
 
+    private boolean isPackageInstalled(String pkg, int userId) {
+        final ApplicationInfo info = mPm.getApplicationInfo(pkg,
+                PackageManager.GET_UNINSTALLED_PACKAGES,
+                userId);
+        if (info == null || (info.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
+            return false;
+        }
+        return true;
+    }
+
     /**
-     * Removes all the restrictions files (res_<packagename>) for a given user.
+     * Removes all the restrictions files (res_<packagename>) for a given user, if all is true,
+     * else removes only those packages that have been uninstalled.
      * Does not do any permissions checking.
      */
-    private void cleanAppRestrictions(int userId) {
+    private void cleanAppRestrictions(int userId, boolean all) {
         synchronized (mPackagesLock) {
             File dir = Environment.getUserSystemDirectory(userId);
             String[] files = dir.list();
@@ -891,13 +907,33 @@ public class UserManagerService extends IUserManager.Stub {
                 if (fileName.startsWith(RESTRICTIONS_FILE_PREFIX)) {
                     File resFile = new File(dir, fileName);
                     if (resFile.exists()) {
-                        resFile.delete();
+                        if (all) {
+                            resFile.delete();
+                        } else {
+                            String pkg = fileName.substring(RESTRICTIONS_FILE_PREFIX.length());
+                            if (!isPackageInstalled(pkg, userId)) {
+                                resFile.delete();
+                            }
+                        }
                     }
                 }
             }
         }
     }
 
+    /**
+     * Removes the app restrictions file for a specific package and user id, if it exists.
+     */
+    private void cleanAppRestrictionsForPackage(String pkg, int userId) {
+        synchronized (mPackagesLock) {
+            File dir = Environment.getUserSystemDirectory(userId);
+            File resFile = new File(dir, RESTRICTIONS_FILE_PREFIX + pkg);
+            if (resFile.exists()) {
+                resFile.delete();
+            }
+        }
+    }
+
     @Override
     public UserInfo createUser(String name, int flags) {
         checkManageUsersPermission("Only the system can create users");
@@ -1168,6 +1204,40 @@ public class UserManagerService extends IUserManager.Stub {
         return true;
     }
 
+    @Override
+    public void removeRestrictions() {
+        checkManageUsersPermission("Only system can remove restrictions");
+        final int userHandle = UserHandle.getCallingUserId();
+        synchronized (mPackagesLock) {
+            // Remove all user restrictions
+            setUserRestrictions(new Bundle(), userHandle);
+            // Remove restrictions pin
+            changeRestrictionsPin(null);
+            // Remove any app restrictions
+            cleanAppRestrictions(userHandle, true);
+        }
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                List<ApplicationInfo> apps =
+                        mPm.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES,
+                                userHandle).getList();
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    for (ApplicationInfo appInfo : apps) {
+                        if ((appInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0
+                                && (appInfo.flags & ApplicationInfo.FLAG_BLOCKED) != 0) {
+                            mPm.setApplicationBlockedSettingAsUser(appInfo.packageName, false,
+                                    userHandle);
+                        }
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+        });
+    }
+
     /*
      * 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
@@ -1372,7 +1442,7 @@ public class UserManagerService extends IUserManager.Stub {
     }
 
     /**
-     * Make a note of the last started time of a user.
+     * Make a note of the last started time of a user and do some cleanup.
      * @param userId the user that was just foregrounded
      */
     public void userForeground(int userId) {
@@ -1387,6 +1457,12 @@ public class UserManagerService extends IUserManager.Stub {
                 user.lastLoggedInTime = now;
                 writeUserLocked(user);
             }
+            // If this is not a restricted profile and there is no restrictions pin, clean up
+            // all restrictions files that might have been left behind, else clean up just the
+            // ones with uninstalled packages
+            RestrictionsPinState pinState = mRestrictionsPinStates.get(userId);
+            final long salt = pinState == null ? 0 : pinState.salt;
+            cleanAppRestrictions(userId, (!user.isRestricted() && salt == 0));
         }
     }
 
@@ -1453,4 +1529,17 @@ public class UserManagerService extends IUserManager.Stub {
             }
         }
     }
+
+    private PackageMonitor mUserPackageMonitor = new PackageMonitor() {
+        @Override
+        public void onPackageRemoved(String pkg, int uid) {
+            final int userId = this.getChangingUserId();
+            // Package could be disappearing because it is being blocked, so also check if
+            // it has been uninstalled.
+            final boolean uninstalled = isPackageDisappearing(pkg) == PACKAGE_PERMANENT_CHANGE;
+            if (uninstalled && userId >= 0 && !isPackageInstalled(pkg, userId)) {
+                cleanAppRestrictionsForPackage(pkg, userId);
+            }
+        }
+    };
 }