diff --git a/Android.mk b/Android.mk index 5f9bea4eb86f4..7520afe87133b 100644 --- a/Android.mk +++ b/Android.mk @@ -117,6 +117,7 @@ LOCAL_SRC_FILES += \ core/java/android/os/IParentalControlCallback.aidl \ core/java/android/os/IPermissionController.aidl \ core/java/android/os/IPowerManager.aidl \ + core/java/android/os/IRemoteCallback.aidl \ core/java/android/os/IVibratorService.aidl \ core/java/android/service/wallpaper/IWallpaperConnection.aidl \ core/java/android/service/wallpaper/IWallpaperEngine.aidl \ diff --git a/api/current.xml b/api/current.xml index 0d16f6769d348..12504612860a7 100644 --- a/api/current.xml +++ b/api/current.xml @@ -16169,6 +16169,19 @@ visibility="public" > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + mManagedDialogs; + private static class ManagedDialog { + Dialog mDialog; + Bundle mArgs; + } + private SparseArray mManagedDialogs; // set by the thread after the constructor and before onCreate(Bundle savedInstanceState) is called. private Instrumentation mInstrumentation; @@ -851,35 +856,41 @@ public class Activity extends ContextThemeWrapper final int[] ids = b.getIntArray(SAVED_DIALOG_IDS_KEY); final int numDialogs = ids.length; - mManagedDialogs = new SparseArray(numDialogs); + mManagedDialogs = new SparseArray(numDialogs); for (int i = 0; i < numDialogs; i++) { final Integer dialogId = ids[i]; Bundle dialogState = b.getBundle(savedDialogKeyFor(dialogId)); if (dialogState != null) { // Calling onRestoreInstanceState() below will invoke dispatchOnCreate // so tell createDialog() not to do it, otherwise we get an exception - final Dialog dialog = createDialog(dialogId, dialogState); - mManagedDialogs.put(dialogId, dialog); - onPrepareDialog(dialogId, dialog); - dialog.onRestoreInstanceState(dialogState); + final ManagedDialog md = new ManagedDialog(); + md.mArgs = b.getBundle(savedDialogArgsKeyFor(dialogId)); + md.mDialog = createDialog(dialogId, dialogState, md.mArgs); + if (md.mDialog != null) { + mManagedDialogs.put(dialogId, md); + onPrepareDialog(dialogId, md.mDialog, md.mArgs); + md.mDialog.onRestoreInstanceState(dialogState); + } } } } - private Dialog createDialog(Integer dialogId, Bundle state) { - final Dialog dialog = onCreateDialog(dialogId); + private Dialog createDialog(Integer dialogId, Bundle state, Bundle args) { + final Dialog dialog = onCreateDialog(dialogId, args); if (dialog == null) { - throw new IllegalArgumentException("Activity#onCreateDialog did " - + "not create a dialog for id " + dialogId); + return null; } dialog.dispatchOnCreate(state); return dialog; } - private String savedDialogKeyFor(int key) { + private static String savedDialogKeyFor(int key) { return SAVED_DIALOG_KEY_PREFIX + key; } + private static String savedDialogArgsKeyFor(int key) { + return SAVED_DIALOG_ARGS_KEY_PREFIX + key; + } /** * Called when activity start-up is complete (after {@link #onStart} @@ -1096,8 +1107,11 @@ public class Activity extends ContextThemeWrapper for (int i = 0; i < numDialogs; i++) { final int key = mManagedDialogs.keyAt(i); ids[i] = key; - final Dialog dialog = mManagedDialogs.valueAt(i); - dialogState.putBundle(savedDialogKeyFor(key), dialog.onSaveInstanceState()); + final ManagedDialog md = mManagedDialogs.valueAt(i); + dialogState.putBundle(savedDialogKeyFor(key), md.mDialog.onSaveInstanceState()); + if (md.mArgs != null) { + dialogState.putBundle(savedDialogArgsKeyFor(key), md.mArgs); + } } dialogState.putIntArray(SAVED_DIALOG_IDS_KEY, ids); @@ -1283,14 +1297,14 @@ public class Activity extends ContextThemeWrapper // dismiss any dialogs we are managing. if (mManagedDialogs != null) { - final int numDialogs = mManagedDialogs.size(); for (int i = 0; i < numDialogs; i++) { - final Dialog dialog = mManagedDialogs.valueAt(i); - if (dialog.isShowing()) { - dialog.dismiss(); + final ManagedDialog md = mManagedDialogs.valueAt(i); + if (md.mDialog.isShowing()) { + md.mDialog.dismiss(); } } + mManagedDialogs = null; } // close any cursors we are managing. @@ -1301,6 +1315,7 @@ public class Activity extends ContextThemeWrapper c.mCursor.close(); } } + mManagedCursors.clear(); } /** @@ -2411,36 +2426,57 @@ public class Activity extends ContextThemeWrapper } /** - * Callback for creating dialogs that are managed (saved and restored) for you - * by the activity. - * - * If you use {@link #showDialog(int)}, the activity will call through to - * this method the first time, and hang onto it thereafter. Any dialog - * that is created by this method will automatically be saved and restored - * for you, including whether it is showing. - * - * If you would like the activity to manage the saving and restoring dialogs - * for you, you should override this method and handle any ids that are - * passed to {@link #showDialog}. - * - * If you would like an opportunity to prepare your dialog before it is shown, - * override {@link #onPrepareDialog(int, Dialog)}. - * - * @param id The id of the dialog. - * @return The dialog - * - * @see #onPrepareDialog(int, Dialog) - * @see #showDialog(int) - * @see #dismissDialog(int) - * @see #removeDialog(int) + * @deprecated Old no-arguments version of {@link #onCreateDialog(int, Bundle)}. */ + @Deprecated protected Dialog onCreateDialog(int id) { return null; } + /** + * Callback for creating dialogs that are managed (saved and restored) for you + * by the activity. The default implementation calls through to + * {@link #onCreateDialog(int)} for compatibility. + * + *

If you use {@link #showDialog(int)}, the activity will call through to + * this method the first time, and hang onto it thereafter. Any dialog + * that is created by this method will automatically be saved and restored + * for you, including whether it is showing. + * + *

If you would like the activity to manage saving and restoring dialogs + * for you, you should override this method and handle any ids that are + * passed to {@link #showDialog}. + * + *

If you would like an opportunity to prepare your dialog before it is shown, + * override {@link #onPrepareDialog(int, Dialog, Bundle)}. + * + * @param id The id of the dialog. + * @param args The dialog arguments provided to {@link #showDialog(int, Bundle)}. + * @return The dialog. If you return null, the dialog will not be created. + * + * @see #onPrepareDialog(int, Dialog, Bundle) + * @see #showDialog(int, Bundle) + * @see #dismissDialog(int) + * @see #removeDialog(int) + */ + protected Dialog onCreateDialog(int id, Bundle args) { + return onCreateDialog(id); + } + + /** + * @deprecated Old no-arguments version of + * {@link #onPrepareDialog(int, Dialog, Bundle)}. + */ + @Deprecated + protected void onPrepareDialog(int id, Dialog dialog) { + dialog.setOwnerActivity(this); + } + /** * Provides an opportunity to prepare a managed dialog before it is being - * shown. + * shown. The default implementation calls through to + * {@link #onPrepareDialog(int, Dialog)} for compatibility. + * *

* Override this if you need to update a managed dialog based on the state * of the application each time it is shown. For example, a time picker @@ -2450,43 +2486,66 @@ public class Activity extends ContextThemeWrapper * * @param id The id of the managed dialog. * @param dialog The dialog. - * @see #onCreateDialog(int) + * @param args The dialog arguments provided to {@link #showDialog(int, Bundle)}. + * @see #onCreateDialog(int, Bundle) * @see #showDialog(int) * @see #dismissDialog(int) * @see #removeDialog(int) */ - protected void onPrepareDialog(int id, Dialog dialog) { - dialog.setOwnerActivity(this); + protected void onPrepareDialog(int id, Dialog dialog, Bundle args) { + onPrepareDialog(id, dialog); } /** - * Show a dialog managed by this activity. A call to {@link #onCreateDialog(int)} + * Simple version of {@link #showDialog(int, Bundle)} that does not + * take any arguments. Simply calls {@link #showDialog(int, Bundle)} + * with null arguments. + */ + public final void showDialog(int id) { + showDialog(id, null); + } + + /** + * Show a dialog managed by this activity. A call to {@link #onCreateDialog(int, Bundle)} * will be made with the same id the first time this is called for a given * id. From thereafter, the dialog will be automatically saved and restored. * - * Each time a dialog is shown, {@link #onPrepareDialog(int, Dialog)} will + *

Each time a dialog is shown, {@link #onPrepareDialog(int, Dialog, Bundle)} will * be made to provide an opportunity to do any timely preparation. * * @param id The id of the managed dialog. - * + * @param args Arguments to pass through to the dialog. These will be saved + * and restored for you. Note that if the dialog is already created, + * {@link #onCreateDialog(int, Bundle)} will not be called with the new + * arguments but {@link #onPrepareDialog(int, Dialog, Bundle)} will be. + * If you need to rebuild the dialog, call {@link #removeDialog(int)}Êfirst. + * @return Returns true if the Dialog was created; false is returned if + * it is not created because {@link #onCreateDialog(int, Bundle)} returns false. + * * @see Dialog - * @see #onCreateDialog(int) - * @see #onPrepareDialog(int, Dialog) + * @see #onCreateDialog(int, Bundle) + * @see #onPrepareDialog(int, Dialog, Bundle) * @see #dismissDialog(int) * @see #removeDialog(int) */ - public final void showDialog(int id) { + public final boolean showDialog(int id, Bundle args) { if (mManagedDialogs == null) { - mManagedDialogs = new SparseArray

(); + mManagedDialogs = new SparseArray(); } - Dialog dialog = mManagedDialogs.get(id); - if (dialog == null) { - dialog = createDialog(id, null); - mManagedDialogs.put(id, dialog); + ManagedDialog md = mManagedDialogs.get(id); + if (md == null) { + md = new ManagedDialog(); + md.mDialog = createDialog(id, null, args); + if (md.mDialog == null) { + return false; + } + mManagedDialogs.put(id, md); } - onPrepareDialog(id, dialog); - dialog.show(); + md.mArgs = args; + onPrepareDialog(id, md.mDialog, args); + md.mDialog.show(); + return true; } /** @@ -2497,21 +2556,21 @@ public class Activity extends ContextThemeWrapper * @throws IllegalArgumentException if the id was not previously shown via * {@link #showDialog(int)}. * - * @see #onCreateDialog(int) - * @see #onPrepareDialog(int, Dialog) + * @see #onCreateDialog(int, Bundle) + * @see #onPrepareDialog(int, Dialog, Bundle) * @see #showDialog(int) * @see #removeDialog(int) */ public final void dismissDialog(int id) { if (mManagedDialogs == null) { throw missingDialog(id); - } - final Dialog dialog = mManagedDialogs.get(id); - if (dialog == null) { + + final ManagedDialog md = mManagedDialogs.get(id); + if (md == null) { throw missingDialog(id); } - dialog.dismiss(); + md.mDialog.dismiss(); } /** @@ -2527,28 +2586,27 @@ public class Activity extends ContextThemeWrapper * Removes any internal references to a dialog managed by this Activity. * If the dialog is showing, it will dismiss it as part of the clean up. * - * This can be useful if you know that you will never show a dialog again and + *

This can be useful if you know that you will never show a dialog again and * want to avoid the overhead of saving and restoring it in the future. * * @param id The id of the managed dialog. * - * @see #onCreateDialog(int) - * @see #onPrepareDialog(int, Dialog) + * @see #onCreateDialog(int, Bundle) + * @see #onPrepareDialog(int, Dialog, Bundle) * @see #showDialog(int) * @see #dismissDialog(int) */ public final void removeDialog(int id) { - if (mManagedDialogs == null) { return; } - final Dialog dialog = mManagedDialogs.get(id); - if (dialog == null) { + final ManagedDialog md = mManagedDialogs.get(id); + if (md == null) { return; } - dialog.dismiss(); + md.mDialog.dismiss(); mManagedDialogs.remove(id); } diff --git a/core/java/android/app/DeviceAdmin.java b/core/java/android/app/DeviceAdmin.java index 9750d6ed7e6c9..88fdab23134a6 100644 --- a/core/java/android/app/DeviceAdmin.java +++ b/core/java/android/app/DeviceAdmin.java @@ -22,6 +22,7 @@ import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.os.Bundle; /** * Base class for implementing a device administration component. This @@ -61,6 +62,27 @@ public class DeviceAdmin extends BroadcastReceiver { public static final String ACTION_DEVICE_ADMIN_ENABLED = "android.app.action.DEVICE_ADMIN_ENABLED"; + /** + * Action sent to a device administrator when the user has requested to + * disable it, but before this has actually been done. This gives you + * a chance to supply a message to the user about the impact of + * disabling your admin, by setting the extra field + * {@link #EXTRA_DISABLE_WARNING} in the result Intent. If not set, + * no warning will be displayed. If set, the given text will be shown + * to the user before they disable your admin. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED + = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED"; + + /** + * A CharSequence that can be shown to the user informing them of the + * impact of disabling your admin. + * + * @see #ACTION_DEVICE_ADMIN_DISABLE_REQUESTED + */ + public static final String EXTRA_DISABLE_WARNING = "android.app.extra.DISABLE_WARNING"; + /** * Action sent to a device administrator when the user has disabled * it. Upon return, the application no longer has access to the @@ -165,6 +187,21 @@ public class DeviceAdmin extends BroadcastReceiver { public void onEnabled(Context context, Intent intent) { } + /** + * Called when the user has asked to disable the administrator, as a result of + * receiving {@link #ACTION_DEVICE_ADMIN_DISABLE_REQUESTED}, giving you + * a chance to present a warning message to them. The message is returned + * as the result; if null is returned (the default implementation), no + * message will be displayed. + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + * @return Return the warning message to display to the user before + * being disabled; if null is returned, no message is displayed. + */ + public CharSequence onDisableRequested(Context context, Intent intent) { + return null; + } + /** * Called prior to the administrator being disabled, as a result of * receiving {@link #ACTION_DEVICE_ADMIN_DISABLED}. Upon return, you @@ -226,6 +263,12 @@ public class DeviceAdmin extends BroadcastReceiver { onPasswordSucceeded(context, intent); } else if (ACTION_DEVICE_ADMIN_ENABLED.equals(action)) { onEnabled(context, intent); + } else if (ACTION_DEVICE_ADMIN_DISABLE_REQUESTED.equals(action)) { + CharSequence res = onDisableRequested(context, intent); + if (res != null) { + Bundle extras = getResultExtras(true); + extras.putCharSequence(EXTRA_DISABLE_WARNING, res); + } } else if (ACTION_DEVICE_ADMIN_DISABLED.equals(action)) { onDisabled(context, intent); } diff --git a/core/java/android/app/DeviceAdminInfo.java b/core/java/android/app/DeviceAdminInfo.java index 92fdbc80908f0..e50db89e8ec46 100644 --- a/core/java/android/app/DeviceAdminInfo.java +++ b/core/java/android/app/DeviceAdminInfo.java @@ -19,7 +19,6 @@ package android.app; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import android.R; import android.content.ComponentName; import android.content.Context; import android.content.pm.ActivityInfo; @@ -111,48 +110,47 @@ public final class DeviceAdminInfo implements Parcelable { /** @hide */ public static class PolicyInfo { + public final int ident; final public String tag; final public int label; final public int description; - public PolicyInfo(String tagIn, int labelIn, int descriptionIn) { + public PolicyInfo(int identIn, String tagIn, int labelIn, int descriptionIn) { + ident = identIn; tag = tagIn; label = labelIn; description = descriptionIn; } } + static ArrayList sPoliciesDisplayOrder = new ArrayList(); static HashMap sKnownPolicies = new HashMap(); static SparseArray sRevKnownPolicies = new SparseArray(); static { - sRevKnownPolicies.put(USES_POLICY_LIMIT_PASSWORD, - new PolicyInfo("limit-password", - com.android.internal.R.string.policylab_limitPassword, - com.android.internal.R.string.policydesc_limitPassword)); - sRevKnownPolicies.put(USES_POLICY_WATCH_LOGIN, - new PolicyInfo("watch-login", - com.android.internal.R.string.policylab_watchLogin, - com.android.internal.R.string.policydesc_watchLogin)); - sRevKnownPolicies.put(USES_POLICY_RESET_PASSWORD, - new PolicyInfo("reset-password", - com.android.internal.R.string.policylab_resetPassword, - com.android.internal.R.string.policydesc_resetPassword)); - sRevKnownPolicies.put(USES_POLICY_LIMIT_UNLOCK, - new PolicyInfo("limit-unlock", - com.android.internal.R.string.policylab_limitUnlock, - com.android.internal.R.string.policydesc_limitUnlock)); - sRevKnownPolicies.put(USES_POLICY_FORCE_LOCK, - new PolicyInfo("force-lock", - com.android.internal.R.string.policylab_forceLock, - com.android.internal.R.string.policydesc_forceLock)); - sRevKnownPolicies.put(USES_POLICY_WIPE_DATA, - new PolicyInfo("wipe-data", - com.android.internal.R.string.policylab_wipeData, - com.android.internal.R.string.policydesc_wipeData)); - for (int i=0; i getUsedPolicies() { ArrayList res = new ArrayList(); - for (int i=0; iYou can optionally include the {@link #EXTRA_ADD_EXPLANATION} + * field to provide the user with additional explanation (in addition + * to your component's description) about what is being added. + * *

Note: the current platform can only have one device administrator * active at a time. If you make this request while there is already * an active administrator, this new request will be canceled automatically. @@ -75,6 +81,14 @@ public class DevicePolicyManager { */ public static final String EXTRA_DEVICE_ADMIN = "android.app.extra.DEVICE_ADMIN"; + /** + * An optional CharSequence providing additional explanation for why the + * admin is being added. + * + * @see #ACTION_ADD_DEVICE_ADMIN + */ + public static final String EXTRA_ADD_EXPLANATION = "android.app.extra.ADD_EXPLANATION"; + /** * Activity action: have the user enter a new password. This activity * should be launched after using {@link #setPasswordMode(ComponentName, int)} @@ -284,6 +298,29 @@ public class DevicePolicyManager { return -1; } + /** + * Set the maximum number of failed password attempts that are allowed + * before the device wipes its data. This is convenience for implementing + * the corresponding functionality with a combination of watching failed + * password attempts and calling {@link #wipeData} upon reaching a certain + * count, and as such requires that you request both + * {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} and + * {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}}. + * + * @param admin Which {@link DeviceAdmin} this request is associated with. + * @param num The number of failed password attempts at which point the + * device will wipe its data. + */ + public void setMaximumFailedPasswordsForWipe(ComponentName admin, int num) { + if (mService != null) { + try { + mService.setMaximumFailedPasswordsForWipe(admin, num); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + /** * Force a new password on the user. This takes effect immediately. The * given password must meet the current password minimum length constraint @@ -448,6 +485,19 @@ public class DevicePolicyManager { } } + /** + * @hide + */ + public void getRemoveWarning(ComponentName admin, RemoteCallback result) { + if (mService != null) { + try { + mService.getRemoveWarning(admin, result); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + /** * @hide */ diff --git a/core/java/android/app/IDevicePolicyManager.aidl b/core/java/android/app/IDevicePolicyManager.aidl index 7e38194807bd2..edb8603554ee0 100644 --- a/core/java/android/app/IDevicePolicyManager.aidl +++ b/core/java/android/app/IDevicePolicyManager.aidl @@ -18,6 +18,7 @@ package android.app; import android.content.ComponentName; +import android.os.RemoteCallback; /** * Internal IPC interface to the device policy service. @@ -32,6 +33,7 @@ interface IDevicePolicyManager { boolean isActivePasswordSufficient(); int getCurrentFailedPasswordAttempts(); + void setMaximumFailedPasswordsForWipe(in ComponentName admin, int num); boolean resetPassword(String password); @@ -44,6 +46,7 @@ interface IDevicePolicyManager { void setActiveAdmin(in ComponentName policyReceiver); ComponentName getActiveAdmin(); + void getRemoveWarning(in ComponentName policyReceiver, in RemoteCallback result); void removeActiveAdmin(in ComponentName policyReceiver); void setActivePasswordState(int mode, int length); diff --git a/core/java/android/os/IRemoteCallback.aidl b/core/java/android/os/IRemoteCallback.aidl new file mode 100644 index 0000000000000..f0c6c73ee8fd8 --- /dev/null +++ b/core/java/android/os/IRemoteCallback.aidl @@ -0,0 +1,25 @@ +/* //device/java/android/android/app/IActivityPendingResult.aidl +** +** Copyright 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. +*/ + +package android.os; + +import android.os.Bundle; + +/** @hide */ +oneway interface IRemoteCallback { + void sendResult(in Bundle data); +} diff --git a/core/java/android/os/MailboxNotAvailableException.java b/core/java/android/os/MailboxNotAvailableException.java deleted file mode 100644 index 574adbd196b13..0000000000000 --- a/core/java/android/os/MailboxNotAvailableException.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2006 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. - */ - -package android.os; - -/** @hide */ -public class MailboxNotAvailableException extends Throwable -{ - /** - * This exception represents the case when a request for a - * named, published mailbox fails because the requested name has not been published - */ - - public - MailboxNotAvailableException() - { - } - - public - MailboxNotAvailableException(String s) - { - super(s); - } -} diff --git a/core/java/android/os/Messenger.java b/core/java/android/os/Messenger.java index 1bc554e03c161..ad55abdde7edd 100644 --- a/core/java/android/os/Messenger.java +++ b/core/java/android/os/Messenger.java @@ -29,7 +29,7 @@ public final class Messenger implements Parcelable { * Create a new Messenger pointing to the given Handler. Any Message * objects sent through this Messenger will appear in the Handler as if * {@link Handler#sendMessage(Message) Handler.sendMessage(Message)} had - * be called directly. + * been called directly. * * @param target The Handler that will receive sent messages. */ diff --git a/core/java/android/os/RemoteCallback.aidl b/core/java/android/os/RemoteCallback.aidl new file mode 100644 index 0000000000000..7ae56f57401c1 --- /dev/null +++ b/core/java/android/os/RemoteCallback.aidl @@ -0,0 +1,19 @@ +/* +** Copyright 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. +*/ + +package android.os; + +parcelable RemoteCallback; diff --git a/core/java/android/os/RemoteCallback.java b/core/java/android/os/RemoteCallback.java new file mode 100644 index 0000000000000..ca95bdf529cd9 --- /dev/null +++ b/core/java/android/os/RemoteCallback.java @@ -0,0 +1,107 @@ +/* + * 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. + */ + +package android.os; + +/** + * TODO: Make this a public API? Let's see how it goes with a few use + * cases first. + * @hide + */ +public abstract class RemoteCallback implements Parcelable { + final Handler mHandler; + final IRemoteCallback mTarget; + + class DeliverResult implements Runnable { + final Bundle mResult; + + DeliverResult(Bundle result) { + mResult = result; + } + + public void run() { + onResult(mResult); + } + } + + class LocalCallback extends IRemoteCallback.Stub { + public void sendResult(Bundle bundle) { + mHandler.post(new DeliverResult(bundle)); + } + } + + static class RemoteCallbackProxy extends RemoteCallback { + RemoteCallbackProxy(IRemoteCallback target) { + super(target); + } + + protected void onResult(Bundle bundle) { + } + } + + public RemoteCallback(Handler handler) { + mHandler = handler; + mTarget = new LocalCallback(); + } + + RemoteCallback(IRemoteCallback target) { + mHandler = null; + mTarget = target; + } + + public void sendResult(Bundle bundle) throws RemoteException { + mTarget.sendResult(bundle); + } + + protected abstract void onResult(Bundle bundle); + + public boolean equals(Object otherObj) { + if (otherObj == null) { + return false; + } + try { + return mTarget.asBinder().equals(((RemoteCallback)otherObj) + .mTarget.asBinder()); + } catch (ClassCastException e) { + } + return false; + } + + public int hashCode() { + return mTarget.asBinder().hashCode(); + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel out, int flags) { + out.writeStrongBinder(mTarget.asBinder()); + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public RemoteCallback createFromParcel(Parcel in) { + IBinder target = in.readStrongBinder(); + return target != null ? new RemoteCallbackProxy( + IRemoteCallback.Stub.asInterface(target)) : null; + } + + public RemoteCallback[] newArray(int size) { + return new RemoteCallback[size]; + } + }; +} diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 31f71d3a8e2a8..259398f0e629d 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1192,10 +1192,10 @@ Watch login attempts - Monitor attempts to login to - the device, in particular to respond to failed login attempts. + Monitor failed attempts to login to + the device, to perform some action. - Reset your password + Reset password Force your password to a new value, requiring the administrator give it to you @@ -1209,12 +1209,12 @@ Force lock Force the device to immediately lock, - requiring that its password is re-entered. + requiring that you re-enter its password. Erase all data Perform a factory reset, deleting - all of your data without any confirmation from you. + all of your data without any confirmation. diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java index fbd53173fd0be..ebd6f3d0bac37 100644 --- a/services/java/com/android/server/DevicePolicyManagerService.java +++ b/services/java/com/android/server/DevicePolicyManagerService.java @@ -23,19 +23,23 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; +import android.app.Activity; import android.app.DeviceAdmin; import android.app.DeviceAdminInfo; import android.app.DevicePolicyManager; import android.app.IDevicePolicyManager; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Binder; +import android.os.Handler; import android.os.IBinder; import android.os.IPowerManager; import android.os.RecoverySystem; +import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; @@ -64,16 +68,68 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { ActiveAdmin mActiveAdmin; static class ActiveAdmin { - ActiveAdmin(DeviceAdminInfo _info) { - info = _info; - } - final DeviceAdminInfo info; - int getUid() { return info.getActivityInfo().applicationInfo.uid; } int passwordMode = DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED; int minimumPasswordLength = 0; long maximumTimeToUnlock = 0; + int maximumFailedPasswordsForWipe = 0; + + ActiveAdmin(DeviceAdminInfo _info) { + info = _info; + } + + int getUid() { return info.getActivityInfo().applicationInfo.uid; } + + void writeToXml(XmlSerializer out) + throws IllegalArgumentException, IllegalStateException, IOException { + if (passwordMode != DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED) { + out.startTag(null, "password-mode"); + out.attribute(null, "value", Integer.toString(passwordMode)); + out.endTag(null, "password-mode"); + if (minimumPasswordLength > 0) { + out.startTag(null, "min-password-length"); + out.attribute(null, "value", Integer.toString(minimumPasswordLength)); + out.endTag(null, "mn-password-length"); + } + } + if (maximumTimeToUnlock != DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED) { + out.startTag(null, "max-time-to-unlock"); + out.attribute(null, "value", Long.toString(maximumTimeToUnlock)); + out.endTag(null, "max-time-to-unlock"); + } + if (maximumFailedPasswordsForWipe != 0) { + out.startTag(null, "max-failed-password-wipe"); + out.attribute(null, "value", Integer.toString(maximumFailedPasswordsForWipe)); + out.endTag(null, "max-failed-password-wipe"); + } + } + + void readFromXml(XmlPullParser parser) + throws XmlPullParserException, IOException { + int outerDepth = parser.getDepth(); + int type; + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + String tag = parser.getName(); + if ("password-mode".equals(tag)) { + passwordMode = Integer.parseInt( + parser.getAttributeValue(null, "value")); + } else if ("min-password-length".equals(tag)) { + minimumPasswordLength = Integer.parseInt( + parser.getAttributeValue(null, "value")); + } else if ("max-time-to-unlock".equals(tag)) { + maximumTimeToUnlock = Long.parseLong( + parser.getAttributeValue(null, "value")); + } else if ("max-failed-password-wipe".equals(tag)) { + maximumFailedPasswordsForWipe = Integer.parseInt( + parser.getAttributeValue(null, "value")); + } + } + } } /** @@ -91,25 +147,42 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return mIPowerManager; } - ActiveAdmin getActiveAdminForCallerLocked(ComponentName who, int reqPolicy) + ActiveAdmin getActiveAdminUncheckedLocked(ComponentName who) { + ActiveAdmin admin = mActiveAdmin; + if (admin != null + && who.getPackageName().equals(admin.info.getActivityInfo().packageName) + && who.getClassName().equals(admin.info.getActivityInfo().name)) { + return admin; + } + return null; + } + + ActiveAdmin getActiveAdminForCallerLocked(ComponentName who) throws SecurityException { - if (mActiveAdmin != null && mActiveAdmin.getUid() == Binder.getCallingUid()) { + ActiveAdmin admin = mActiveAdmin; + if (admin != null && admin.getUid() == Binder.getCallingUid()) { if (who != null) { - if (!who.getPackageName().equals(mActiveAdmin.info.getActivityInfo().packageName) - || !who.getClassName().equals(mActiveAdmin.info.getActivityInfo().name)) { + if (!who.getPackageName().equals(admin.info.getActivityInfo().packageName) + || !who.getClassName().equals(admin.info.getActivityInfo().name)) { throw new SecurityException("Current admin is not " + who); } } - if (!mActiveAdmin.info.usesPolicy(reqPolicy)) { - throw new SecurityException("Admin " + mActiveAdmin.info.getComponent() - + " did not specify uses-policy for: " - + mActiveAdmin.info.getTagForPolicy(reqPolicy)); - } return mActiveAdmin; } throw new SecurityException("Current admin is not owned by uid " + Binder.getCallingUid()); } + ActiveAdmin getActiveAdminForCallerLocked(ComponentName who, int reqPolicy) + throws SecurityException { + ActiveAdmin admin = getActiveAdminForCallerLocked(who); + if (!admin.info.usesPolicy(reqPolicy)) { + throw new SecurityException("Admin " + admin.info.getComponent() + + " did not specify uses-policy for: " + + admin.info.getTagForPolicy(reqPolicy)); + } + return admin; + } + void sendAdminCommandLocked(ActiveAdmin admin, String action) { Intent intent = new Intent(action); intent.setComponent(admin.info.getComponent()); @@ -182,25 +255,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (ap != null) { out.startTag(null, "admin"); out.attribute(null, "name", ap.info.getComponent().flattenToString()); - if (ap.passwordMode != DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED) { - out.startTag(null, "password-mode"); - out.attribute(null, "value", Integer.toString(ap.passwordMode)); - out.endTag(null, "password-mode"); - if (ap.minimumPasswordLength > 0) { - out.startTag(null, "min-password-length"); - out.attribute(null, "value", Integer.toString(ap.minimumPasswordLength)); - out.endTag(null, "mn-password-length"); - } - } - if (ap.maximumTimeToUnlock != DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED) { - out.startTag(null, "max-time-to-unlock"); - out.attribute(null, "value", Long.toString(ap.maximumTimeToUnlock)); - out.endTag(null, "max-time-to-unlock"); - } + ap.writeToXml(out); out.endTag(null, "admin"); } out.endTag(null, "policies"); + if (mFailedPasswordAttempts != 0) { + out.startTag(null, "failed-password-attempts"); + out.attribute(null, "value", Integer.toString(mFailedPasswordAttempts)); + out.endTag(null, "failed-password-attempts"); + } + out.endDocument(); stream.close(); journal.commit(); @@ -220,51 +285,41 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { JournaledFile journal = makeJournaledFile(); FileInputStream stream = null; File file = journal.chooseForRead(); - boolean success = false; try { stream = new FileInputStream(file); XmlPullParser parser = Xml.newPullParser(); parser.setInput(stream, null); - int type = parser.next(); - while (type != XmlPullParser.START_TAG) { - type = parser.next(); + int type; + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && type != XmlPullParser.START_TAG) { } String tag = parser.getName(); - if ("policies".equals(tag)) { - ActiveAdmin ap = null; - do { - type = parser.next(); - if (type == XmlPullParser.START_TAG) { - tag = parser.getName(); - if (ap == null) { - if ("admin".equals(tag)) { - DeviceAdminInfo dai = findAdmin( - ComponentName.unflattenFromString( - parser.getAttributeValue(null, "name"))); - if (dai != null) { - ap = new ActiveAdmin(dai); - } - } - } else if ("password-mode".equals(tag)) { - ap.passwordMode = Integer.parseInt( - parser.getAttributeValue(null, "value")); - } else if ("min-password-length".equals(tag)) { - ap.minimumPasswordLength = Integer.parseInt( - parser.getAttributeValue(null, "value")); - } else if ("max-time-to-unlock".equals(tag)) { - ap.maximumTimeToUnlock = Long.parseLong( - parser.getAttributeValue(null, "value")); - } - } else if (type == XmlPullParser.END_TAG) { - tag = parser.getName(); - if (ap != null && "admin".equals(tag)) { - mActiveAdmin = ap; - ap = null; - } + if (!"policies".equals(tag)) { + throw new XmlPullParserException( + "Settings do not start with policies tag: found " + tag); + } + type = parser.next(); + int outerDepth = parser.getDepth(); + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + tag = parser.getName(); + if ("admin".equals(tag)) { + DeviceAdminInfo dai = findAdmin( + ComponentName.unflattenFromString( + parser.getAttributeValue(null, "name"))); + if (dai != null) { + ActiveAdmin ap = new ActiveAdmin(dai); + ap.readFromXml(parser); + mActiveAdmin = ap; } - } while (type != XmlPullParser.END_DOCUMENT); - success = true; + } else if ("failed-password-attempts".equals(tag)) { + mFailedPasswordAttempts = Integer.parseInt( + parser.getAttributeValue(null, "value")); + } } } catch (NullPointerException e) { Log.w(TAG, "failed parsing " + file + " " + e); @@ -285,10 +340,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Ignore } - if (!success) { - Log.w(TAG, "No valid start tag found in policies file"); - } - long timeMs = getMaximumTimeToLock(); if (timeMs <= 0) { timeMs = Integer.MAX_VALUE; @@ -418,6 +469,27 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + public void setMaximumFailedPasswordsForWipe(ComponentName who, int num) { + synchronized (this) { + // This API can only be called by an active device admin, + // so try to retrieve it to check that the caller is one. + getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_WIPE_DATA); + ActiveAdmin ap = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_WATCH_LOGIN); + if (ap.maximumFailedPasswordsForWipe != num) { + ap.maximumFailedPasswordsForWipe = num; + saveSettingsLocked(); + } + } + } + + public int getMaximumFailedPasswordsForWipe() { + synchronized (this) { + return mActiveAdmin != null ? mActiveAdmin.maximumFailedPasswordsForWipe : 0; + } + } + public boolean resetPassword(String password) { int mode; synchronized (this) { @@ -488,20 +560,53 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + void wipeDataLocked(int flags) { + try { + RecoverySystem.rebootWipeUserData(mContext); + } catch (IOException e) { + Log.w(TAG, "Failed requesting data wipe", e); + } + } + public void wipeData(int flags) { synchronized (this) { // This API can only be called by an active device admin, // so try to retrieve it to check that the caller is one. getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_WIPE_DATA); + long ident = Binder.clearCallingIdentity(); + try { + wipeDataLocked(flags); + } finally { + Binder.restoreCallingIdentity(ident); + } } - long ident = Binder.clearCallingIdentity(); - try { - RecoverySystem.rebootWipeUserData(mContext); - } catch (IOException e) { - Log.w(TAG, "Failed requesting data wipe", e); - } finally { - Binder.restoreCallingIdentity(ident); + } + + public void getRemoveWarning(ComponentName comp, final RemoteCallback result) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BIND_DEVICE_ADMIN, null); + + synchronized (this) { + ActiveAdmin admin = getActiveAdminUncheckedLocked(comp); + if (admin == null) { + try { + result.sendResult(null); + } catch (RemoteException e) { + } + return; + } + Intent intent = new Intent(DeviceAdmin.ACTION_DEVICE_ADMIN_DISABLE_REQUESTED); + intent.setComponent(admin.info.getComponent()); + mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + try { + result.sendResult(getResultExtras(false)); + } catch (RemoteException e) { + } + } + }, null, Activity.RESULT_OK, null, null); } } @@ -516,7 +621,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { try { mActivePasswordMode = mode; mActivePasswordLength = length; - mFailedPasswordAttempts = 0; + if (mFailedPasswordAttempts != 0) { + mFailedPasswordAttempts = 0; + saveSettingsLocked(); + } sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_CHANGED, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); } finally { @@ -534,6 +642,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { long ident = Binder.clearCallingIdentity(); try { mFailedPasswordAttempts++; + saveSettingsLocked(); + int max = getMaximumFailedPasswordsForWipe(); + if (max > 0 && mFailedPasswordAttempts >= max) { + wipeDataLocked(0); + } sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_FAILED, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN); } finally { @@ -551,6 +664,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { long ident = Binder.clearCallingIdentity(); try { mFailedPasswordAttempts = 0; + saveSettingsLocked(); sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_SUCCEEDED, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN); } finally {