SecurityPolicy.java revision 6d0016229adc13fefe68820fe4d6e46f530952ba
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.email;
18
19import com.android.email.activity.setup.AccountSecurity;
20import com.android.email.provider.EmailContent;
21import com.android.email.provider.EmailContent.Account;
22import com.android.email.provider.EmailContent.AccountColumns;
23import com.android.email.service.MailService;
24
25import android.app.Notification;
26import android.app.NotificationManager;
27import android.app.PendingIntent;
28import android.app.admin.DeviceAdminReceiver;
29import android.app.admin.DevicePolicyManager;
30import android.content.ComponentName;
31import android.content.ContentResolver;
32import android.content.ContentUris;
33import android.content.ContentValues;
34import android.content.Context;
35import android.content.Intent;
36import android.database.Cursor;
37import android.net.Uri;
38import android.util.Log;
39
40/**
41 * Utility functions to support reading and writing security policies, and handshaking the device
42 * into and out of various security states.
43 */
44public class SecurityPolicy {
45
46    private static SecurityPolicy sInstance = null;
47    private Context mContext;
48    private DevicePolicyManager mDPM;
49    private ComponentName mAdminName;
50    private PolicySet mAggregatePolicy;
51    private boolean mNotificationActive;
52    private boolean mAdminEnabled;
53
54    /* package */ static final PolicySet NO_POLICY_SET =
55            new PolicySet(0, PolicySet.PASSWORD_MODE_NONE, 0, 0, false);
56
57    /**
58     * This projection on Account is for scanning/reading
59     */
60    private static final String[] ACCOUNT_SECURITY_PROJECTION = new String[] {
61        AccountColumns.ID, AccountColumns.SECURITY_FLAGS
62    };
63    private static final int ACCOUNT_SECURITY_COLUMN_FLAGS = 1;
64    // Note, this handles the NULL case to deal with older accounts where the column was added
65    private static final String WHERE_ACCOUNT_SECURITY_NONZERO =
66        Account.SECURITY_FLAGS + " IS NOT NULL AND " + Account.SECURITY_FLAGS + "!=0";
67
68    /**
69     * This projection on Account is for clearing the "security hold" column.  Also includes
70     * the security flags column, so we can use it for selecting.
71     */
72    private static final String[] ACCOUNT_FLAGS_PROJECTION = new String[] {
73        AccountColumns.ID, AccountColumns.FLAGS, AccountColumns.SECURITY_FLAGS
74    };
75    private static final int ACCOUNT_FLAGS_COLUMN_ID = 0;
76    private static final int ACCOUNT_FLAGS_COLUMN_FLAGS = 1;
77
78   /**
79    * These are hardcoded limits based on knowledge of the current DevicePolicyManager
80    * and screen lock mechanisms.  Wherever possible, these should be replaced with queries of
81    * dynamic capabilities of the device (e.g. what password modes are supported?)
82    */
83   private static final int LIMIT_MIN_PASSWORD_LENGTH = 16;
84   private static final int LIMIT_PASSWORD_MODE = PolicySet.PASSWORD_MODE_STRONG;
85   private static final int LIMIT_SCREENLOCK_TIME = PolicySet.SCREEN_LOCK_TIME_MAX;
86
87    /**
88     * Get the security policy instance
89     */
90    public synchronized static SecurityPolicy getInstance(Context context) {
91        if (sInstance == null) {
92            sInstance = new SecurityPolicy(context);
93        }
94        return sInstance;
95    }
96
97    /**
98     * Private constructor (one time only)
99     */
100    private SecurityPolicy(Context context) {
101        mContext = context;
102        mDPM = null;
103        mAdminName = new ComponentName(context, PolicyAdmin.class);
104        mAggregatePolicy = null;
105        mNotificationActive = false;
106    }
107
108    /**
109     * For testing only: Inject context into already-created instance
110     */
111    /* package */ void setContext(Context context) {
112        mContext = context;
113    }
114
115    /**
116     * Compute the aggregate policy for all accounts that require it, and record it.
117     *
118     * The business logic is as follows:
119     *  min password length         take the max
120     *  password mode               take the max (strongest mode)
121     *  max password fails          take the min
122     *  max screen lock time        take the min
123     *  require remote wipe         take the max (logical or)
124     *
125     * @return a policy representing the strongest aggregate.  If no policy sets are defined,
126     * a lightweight "nothing required" policy will be returned.  Never null.
127     */
128    /* package */ PolicySet computeAggregatePolicy() {
129        boolean policiesFound = false;
130
131        int minPasswordLength = Integer.MIN_VALUE;
132        int passwordMode = Integer.MIN_VALUE;
133        int maxPasswordFails = Integer.MAX_VALUE;
134        int maxScreenLockTime = Integer.MAX_VALUE;
135        boolean requireRemoteWipe = false;
136
137        Cursor c = mContext.getContentResolver().query(Account.CONTENT_URI,
138                ACCOUNT_SECURITY_PROJECTION, WHERE_ACCOUNT_SECURITY_NONZERO, null, null);
139        try {
140            while (c.moveToNext()) {
141                int flags = c.getInt(ACCOUNT_SECURITY_COLUMN_FLAGS);
142                if (flags != 0) {
143                    PolicySet p = new PolicySet(flags);
144                    minPasswordLength = Math.max(p.mMinPasswordLength, minPasswordLength);
145                    passwordMode  = Math.max(p.mPasswordMode, passwordMode);
146                    if (p.mMaxPasswordFails > 0) {
147                        maxPasswordFails = Math.min(p.mMaxPasswordFails, maxPasswordFails);
148                    }
149                    if (p.mMaxScreenLockTime > 0) {
150                        maxScreenLockTime = Math.min(p.mMaxScreenLockTime, maxScreenLockTime);
151                    }
152                    requireRemoteWipe |= p.mRequireRemoteWipe;
153                    policiesFound = true;
154                }
155            }
156        } finally {
157            c.close();
158        }
159        if (policiesFound) {
160            // final cleanup pass converts any untouched min/max values to zero (not specified)
161            if (minPasswordLength == Integer.MIN_VALUE) minPasswordLength = 0;
162            if (passwordMode == Integer.MIN_VALUE) passwordMode = 0;
163            if (maxPasswordFails == Integer.MAX_VALUE) maxPasswordFails = 0;
164            if (maxScreenLockTime == Integer.MAX_VALUE) maxScreenLockTime = 0;
165
166            return new PolicySet(minPasswordLength, passwordMode, maxPasswordFails,
167                    maxScreenLockTime, requireRemoteWipe);
168        } else {
169            return NO_POLICY_SET;
170        }
171    }
172
173    /**
174     * Return updated aggregate policy, from cached value if possible
175     */
176    public synchronized PolicySet getAggregatePolicy() {
177        if (mAggregatePolicy == null) {
178            mAggregatePolicy = computeAggregatePolicy();
179        }
180        return mAggregatePolicy;
181    }
182
183    /**
184     * Get the dpm.  This mainly allows us to make some utility calls without it, for testing.
185     */
186    private synchronized DevicePolicyManager getDPM() {
187        if (mDPM == null) {
188            mDPM = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
189        }
190        return mDPM;
191    }
192
193    /**
194     * API: Query used to determine if a given policy is "possible" (irrespective of current
195     * device state.  This is used when creating new accounts.
196     *
197     * TODO: This is hardcoded based on knowledge of the current DevicePolicyManager
198     * and screen lock mechanisms.  It would be nice to replace these tests with something
199     * more dynamic.
200     *
201     * @param policies the policies requested
202     * @return true if the policies are supported, false if not supported
203     */
204    public boolean isSupported(PolicySet policies) {
205        if (policies.mMinPasswordLength > LIMIT_MIN_PASSWORD_LENGTH) {
206            return false;
207        }
208        if (policies.mPasswordMode > LIMIT_PASSWORD_MODE ) {
209            return false;
210        }
211        // No limit on password fail count
212        if (policies.mMaxScreenLockTime > LIMIT_SCREENLOCK_TIME ) {
213            return false;
214        }
215        // No limit on remote wipe capable
216
217        return true;
218    }
219
220    /**
221     * API: Report that policies may have been updated due to rewriting values in an Account.
222     * @param accountId the account that has been updated, -1 if unknown/deleted
223     */
224    public synchronized void updatePolicies(long accountId) {
225        mAggregatePolicy = null;
226    }
227
228    /**
229     * API: Report that policies may have been updated *and* the caller vouches that the
230     * change is a reduction in policies.  This forces an immediate change to device state.
231     * Typically used when deleting accounts, although we may use it for server-side policy
232     * rollbacks.
233     */
234    public void reducePolicies() {
235        updatePolicies(-1);
236        setActivePolicies();
237    }
238
239    /**
240     * API: Query used to determine if a given policy is "active" (the device is operating at
241     * the required security level).
242     *
243     * This can be used when syncing a specific account, by passing a specific set of policies
244     * for that account.  Or, it can be used at any time to compare the device
245     * state against the aggregate set of device policies stored in all accounts.
246     *
247     * This method is for queries only, and does not trigger any change in device state.
248     *
249     * @param policies the policies requested, or null to check aggregate stored policies
250     * @return true if the policies are active, false if not active
251     */
252    public boolean isActive(PolicySet policies) {
253        // select aggregate set if needed
254        if (policies == null) {
255            policies = getAggregatePolicy();
256        }
257        // quick check for the "empty set" of no policies
258        if (policies == NO_POLICY_SET) {
259            return true;
260        }
261        DevicePolicyManager dpm = getDPM();
262        if (dpm.isAdminActive(mAdminName)) {
263            // check each policy explicitly
264            if (policies.mMinPasswordLength > 0) {
265                if (dpm.getPasswordMinimumLength(mAdminName) < policies.mMinPasswordLength) {
266                    return false;
267                }
268            }
269            if (policies.mPasswordMode > 0) {
270                if (dpm.getPasswordQuality(mAdminName) < policies.getDPManagerPasswordQuality()) {
271                    return false;
272                }
273                if (!dpm.isActivePasswordSufficient()) {
274                    return false;
275                }
276            }
277            if (policies.mMaxScreenLockTime > 0) {
278                // Note, we use seconds, dpm uses milliseconds
279                if (dpm.getMaximumTimeToLock(mAdminName) > policies.mMaxScreenLockTime * 1000) {
280                    return false;
281                }
282            }
283            // password failures are counted locally - no test required here
284            // no check required for remote wipe (it's supported, if we're the admin)
285
286            // making it this far means we passed!
287            return true;
288        }
289        // return false, not active
290        return false;
291    }
292
293    /**
294     * Set the requested security level based on the aggregate set of requests.
295     * If the set is empty, we release our device administration.  If the set is non-empty,
296     * we only proceed if we are already active as an admin.
297     */
298    public void setActivePolicies() {
299        DevicePolicyManager dpm = getDPM();
300        // compute aggregate set of policies
301        PolicySet policies = getAggregatePolicy();
302        // if empty set, detach from policy manager
303        if (policies == NO_POLICY_SET) {
304            dpm.removeActiveAdmin(mAdminName);
305        } else if (dpm.isAdminActive(mAdminName)) {
306            // set each policy in the policy manager
307            // password mode & length
308            dpm.setPasswordQuality(mAdminName, policies.getDPManagerPasswordQuality());
309            dpm.setPasswordMinimumLength(mAdminName, policies.mMinPasswordLength);
310            // screen lock time
311            dpm.setMaximumTimeToLock(mAdminName, policies.mMaxScreenLockTime * 1000);
312            // local wipe (failed passwords limit)
313            dpm.setMaximumFailedPasswordsForWipe(mAdminName, policies.mMaxPasswordFails);
314        }
315    }
316
317    /**
318     * API: Set/Clear the "hold" flag in any account.  This flag serves a dual purpose:
319     * Setting it gives us an indication that it was blocked, and clearing it gives EAS a
320     * signal to try syncing again.
321     */
322    public void setAccountHoldFlag(Account account, boolean newState) {
323        if (newState) {
324            account.mFlags |= Account.FLAGS_SECURITY_HOLD;
325        } else {
326            account.mFlags &= ~Account.FLAGS_SECURITY_HOLD;
327        }
328        ContentValues cv = new ContentValues();
329        cv.put(AccountColumns.FLAGS, account.mFlags);
330        account.update(mContext, cv);
331    }
332
333    /**
334     * Clear all account hold flags that are set.  This will trigger watchers, and in particular
335     * will cause EAS to try and resync the account(s).
336     */
337    public void clearAccountHoldFlags() {
338        ContentResolver resolver = mContext.getContentResolver();
339        Cursor c = resolver.query(Account.CONTENT_URI, ACCOUNT_FLAGS_PROJECTION,
340                WHERE_ACCOUNT_SECURITY_NONZERO, null, null);
341        try {
342            while (c.moveToNext()) {
343                int flags = c.getInt(ACCOUNT_FLAGS_COLUMN_FLAGS);
344                if (0 != (flags & Account.FLAGS_SECURITY_HOLD)) {
345                    ContentValues cv = new ContentValues();
346                    cv.put(AccountColumns.FLAGS, flags & ~Account.FLAGS_SECURITY_HOLD);
347                    long accountId = c.getLong(ACCOUNT_FLAGS_COLUMN_ID);
348                    Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId);
349                    resolver.update(uri, cv, null, null);
350                }
351            }
352        } finally {
353            c.close();
354        }
355    }
356
357    /**
358     * API: Sync service should call this any time a sync fails due to isActive() returning false.
359     * This will kick off the notify-acquire-admin-state process and/or increase the security level.
360     * The caller needs to write the required policies into this account before making this call.
361     * Should not be called from UI thread - uses DB lookups to prepare new notifications
362     *
363     * @param accountId the account for which sync cannot proceed
364     */
365    public void policiesRequired(long accountId) {
366        Account account = EmailContent.Account.restoreAccountWithId(mContext, accountId);
367
368        // Mark the account as "on hold".
369        setAccountHoldFlag(account, true);
370
371        // Put up a notification (unless there already is one)
372        synchronized (this) {
373            if (mNotificationActive) {
374                // no need to do anything - we've already been notified, and we've already
375                // put up a notification
376                return;
377            } else {
378                // Prepare & post a notification
379                // record that we're watching this one
380                mNotificationActive = true;
381            }
382        }
383        // At this point, we will put up a notification
384
385        String tickerText = mContext.getString(R.string.security_notification_ticker_fmt,
386                account.getDisplayName());
387        String contentTitle = mContext.getString(R.string.security_notification_content_title);
388        String contentText = account.getDisplayName();
389        String ringtoneString = account.getRingtone();
390        Uri ringTone = (ringtoneString == null) ? null : Uri.parse(ringtoneString);
391        boolean vibrate = 0 != (account.mFlags & Account.FLAGS_VIBRATE);
392
393        Intent intent = AccountSecurity.actionUpdateSecurityIntent(mContext, accountId);
394        PendingIntent pending =
395            PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
396
397        Notification notification = new Notification(R.drawable.stat_notify_email_generic,
398                tickerText, System.currentTimeMillis());
399        notification.setLatestEventInfo(mContext, contentTitle, contentText, pending);
400
401        // Use the account's notification rules for sound & vibrate (but always notify)
402        notification.sound = ringTone;
403        if (vibrate) {
404            notification.defaults |= Notification.DEFAULT_VIBRATE;
405        }
406        notification.flags |= Notification.FLAG_SHOW_LIGHTS;
407        notification.defaults |= Notification.DEFAULT_LIGHTS;
408
409        NotificationManager notificationManager =
410            (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
411        notificationManager.notify(MailService.NOTIFICATION_ID_SECURITY_NEEDED, notification);
412    }
413
414    /**
415     * Called from the notification's intent receiver to register that the notification can be
416     * cleared now.
417     */
418    public synchronized void clearNotification(long accountId) {
419        NotificationManager notificationManager =
420            (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
421        notificationManager.cancel(MailService.NOTIFICATION_ID_SECURITY_NEEDED);
422        mNotificationActive = false;
423    }
424
425    /**
426     * API: Remote wipe (from server).  This is final, there is no confirmation.  It will only
427     * return to the caller if there is an unexpected failure.
428     */
429    public void remoteWipe() {
430        DevicePolicyManager dpm = getDPM();
431        if (dpm.isAdminActive(mAdminName)) {
432            dpm.wipeData(0);
433        } else {
434            Log.d(Email.LOG_TAG, "Could not remote wipe because not device admin.");
435        }
436    }
437
438    /**
439     * Class for tracking policies and reading/writing into accounts
440     */
441    public static class PolicySet {
442
443        // Security (provisioning) flags
444            // bits 0..4: password length (0=no password required)
445        private static final int PASSWORD_LENGTH_MASK = 31;
446        private static final int PASSWORD_LENGTH_SHIFT = 0;
447        public static final int PASSWORD_LENGTH_MAX = 31;
448            // bits 5..8: password mode
449        private static final int PASSWORD_MODE_SHIFT = 5;
450        private static final int PASSWORD_MODE_MASK = 15 << PASSWORD_MODE_SHIFT;
451        public static final int PASSWORD_MODE_NONE = 0 << PASSWORD_MODE_SHIFT;
452        public static final int PASSWORD_MODE_SIMPLE = 1 << PASSWORD_MODE_SHIFT;
453        public static final int PASSWORD_MODE_STRONG = 2 << PASSWORD_MODE_SHIFT;
454            // bits 9..13: password failures -> wipe device (0=disabled)
455        private static final int PASSWORD_MAX_FAILS_SHIFT = 9;
456        private static final int PASSWORD_MAX_FAILS_MASK = 31 << PASSWORD_MAX_FAILS_SHIFT;
457        public static final int PASSWORD_MAX_FAILS_MAX = 31;
458            // bits 14..24: seconds to screen lock (0=not required)
459        private static final int SCREEN_LOCK_TIME_SHIFT = 14;
460        private static final int SCREEN_LOCK_TIME_MASK = 2047 << SCREEN_LOCK_TIME_SHIFT;
461        public static final int SCREEN_LOCK_TIME_MAX = 2047;
462            // bit 25: remote wipe capability required
463        private static final int REQUIRE_REMOTE_WIPE = 1 << 25;
464
465        public final int mMinPasswordLength;
466        public final int mPasswordMode;
467        public final int mMaxPasswordFails;
468        public final int mMaxScreenLockTime;
469        public final boolean mRequireRemoteWipe;
470
471        /**
472         * Create from raw values.
473         * @param minPasswordLength (0=not enforced)
474         * @param passwordMode
475         * @param maxPasswordFails (0=not enforced)
476         * @param maxScreenLockTime in seconds (0=not enforced)
477         * @param requireRemoteWipe
478         * @throws IllegalArgumentException when any arguments are outside of legal ranges.
479         */
480        public PolicySet(int minPasswordLength, int passwordMode, int maxPasswordFails,
481                int maxScreenLockTime, boolean requireRemoteWipe) throws IllegalArgumentException {
482            if (minPasswordLength > PASSWORD_LENGTH_MAX) {
483                throw new IllegalArgumentException("password length");
484            }
485            if (passwordMode < PASSWORD_MODE_NONE
486                    || passwordMode > PASSWORD_MODE_STRONG) {
487                throw new IllegalArgumentException("password mode");
488            }
489            if (maxPasswordFails > PASSWORD_MAX_FAILS_MAX) {
490                throw new IllegalArgumentException("password max fails");
491            }
492            if (maxScreenLockTime > SCREEN_LOCK_TIME_MAX) {
493                throw new IllegalArgumentException("max screen lock time");
494            }
495
496            mMinPasswordLength = minPasswordLength;
497            mPasswordMode = passwordMode;
498            mMaxPasswordFails = maxPasswordFails;
499            mMaxScreenLockTime = maxScreenLockTime;
500            mRequireRemoteWipe = requireRemoteWipe;
501        }
502
503        /**
504         * Create from values encoded in an account
505         * @param account
506         */
507        public PolicySet(Account account) {
508            this(account.mSecurityFlags);
509        }
510
511        /**
512         * Create from values encoded in an account flags int
513         */
514        public PolicySet(int flags) {
515            mMinPasswordLength =
516                (flags & PASSWORD_LENGTH_MASK) >> PASSWORD_LENGTH_SHIFT;
517            mPasswordMode =
518                (flags & PASSWORD_MODE_MASK);
519            mMaxPasswordFails =
520                (flags & PASSWORD_MAX_FAILS_MASK) >> PASSWORD_MAX_FAILS_SHIFT;
521            mMaxScreenLockTime =
522                (flags & SCREEN_LOCK_TIME_MASK) >> SCREEN_LOCK_TIME_SHIFT;
523            mRequireRemoteWipe = 0 != (flags & REQUIRE_REMOTE_WIPE);
524        }
525
526        /**
527         * Helper to map our internal encoding to DevicePolicyManager password modes.
528         */
529        public int getDPManagerPasswordQuality() {
530            switch (mPasswordMode) {
531                case PASSWORD_MODE_SIMPLE:
532                    return DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
533                case PASSWORD_MODE_STRONG:
534                    return DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
535                default:
536                    return DevicePolicyManager .PASSWORD_QUALITY_UNSPECIFIED;
537            }
538        }
539
540        /**
541         * Record flags (and a sync key for the flags) into an Account
542         * Note: the hash code is defined as the encoding used in Account
543         *
544         * @param account to write the values mSecurityFlags and mSecuritySyncKey
545         * @param syncKey the value to write into the account's mSecuritySyncKey
546         * @param update if true, also writes the account back to the provider (updating only
547         *  the fields changed by this API)
548         * @param context a context for writing to the provider
549         * @return true if the actual policies changed, false if no change (note, sync key
550         *  does not affect this)
551         */
552        public boolean writeAccount(Account account, String syncKey, boolean update,
553                Context context) {
554            int newFlags = hashCode();
555            boolean dirty = (newFlags != account.mSecurityFlags);
556            account.mSecurityFlags = newFlags;
557            account.mSecuritySyncKey = syncKey;
558            if (update) {
559                if (account.isSaved()) {
560                    ContentValues cv = new ContentValues();
561                    cv.put(AccountColumns.SECURITY_FLAGS, account.mSecurityFlags);
562                    cv.put(AccountColumns.SECURITY_SYNC_KEY, account.mSecuritySyncKey);
563                    account.update(context, cv);
564                } else {
565                    account.save(context);
566                }
567            }
568            return dirty;
569        }
570
571        @Override
572        public boolean equals(Object o) {
573            if (o instanceof PolicySet) {
574                PolicySet other = (PolicySet)o;
575                return (this.mMinPasswordLength == other.mMinPasswordLength)
576                        && (this.mPasswordMode == other.mPasswordMode)
577                        && (this.mMaxPasswordFails == other.mMaxPasswordFails)
578                        && (this.mMaxScreenLockTime == other.mMaxScreenLockTime)
579                        && (this.mRequireRemoteWipe == other.mRequireRemoteWipe);
580            }
581            return false;
582        }
583
584        /**
585         * Note: the hash code is defined as the encoding used in Account
586         */
587        @Override
588        public int hashCode() {
589            int flags = 0;
590            flags = mMinPasswordLength << PASSWORD_LENGTH_SHIFT;
591            flags |= mPasswordMode;
592            flags |= mMaxPasswordFails << PASSWORD_MAX_FAILS_SHIFT;
593            flags |= mMaxScreenLockTime << SCREEN_LOCK_TIME_SHIFT;
594            if (mRequireRemoteWipe) {
595                flags |= REQUIRE_REMOTE_WIPE;
596            }
597            return flags;
598        }
599
600        @Override
601        public String toString() {
602            return "{ " + "pw-len-min=" + mMinPasswordLength + " pw-mode=" + mPasswordMode
603                    + " pw-fails-max=" + mMaxPasswordFails + " screenlock-max="
604                    + mMaxScreenLockTime + " remote-wipe-req=" + mRequireRemoteWipe + "}";
605        }
606    }
607
608    /**
609     * If we are not the active device admin, try to become so.
610     *
611     * @return true if we are already active, false if we are not
612     */
613    public boolean isActiveAdmin() {
614        DevicePolicyManager dpm = getDPM();
615        return dpm.isAdminActive(mAdminName);
616    }
617
618    /**
619     * Report admin component name - for making calls into device policy manager
620     */
621    public ComponentName getAdminComponent() {
622        return mAdminName;
623    }
624
625    /**
626     * Internal handler for enabled/disabled transitions.  Handles DeviceAdmin.onEnabled and
627     * and DeviceAdmin.onDisabled.
628     */
629    /* package */ void onAdminEnabled(boolean isEnabled) {
630        if (isEnabled && !mAdminEnabled) {
631            // TODO: transition to enabled state
632        } else if (!isEnabled && mAdminEnabled) {
633            // transition to disabled state
634            // Response:  clear *all* security state information from the accounts, forcing
635            // them back to the initial configurations requiring policy administration
636            ContentValues cv = new ContentValues();
637            cv.put(AccountColumns.SECURITY_FLAGS, 0);
638            cv.putNull(AccountColumns.SECURITY_SYNC_KEY);
639            mContext.getContentResolver().update(Account.CONTENT_URI, cv, null, null);
640            updatePolicies(-1);
641        }
642        mAdminEnabled = isEnabled;
643    }
644
645    /**
646     * Device Policy administrator.  This is primarily a listener for device state changes.
647     * Note:  This is instantiated by incoming messages.
648     * Note:  We do not implement onPasswordFailed() because the default behavior of the
649     *        DevicePolicyManager - complete local wipe after 'n' failures - is sufficient.
650     */
651    public static class PolicyAdmin extends DeviceAdminReceiver {
652
653        /**
654         * Called after the administrator is first enabled.
655         */
656        @Override
657        public void onEnabled(Context context, Intent intent) {
658            SecurityPolicy.getInstance(context).onAdminEnabled(true);
659        }
660
661        /**
662         * Called prior to the administrator being disabled.
663         */
664        @Override
665        public void onDisabled(Context context, Intent intent) {
666            SecurityPolicy.getInstance(context).onAdminEnabled(false);
667        }
668
669        /**
670         * Called after the user has changed their password.
671         */
672        @Override
673        public void onPasswordChanged(Context context, Intent intent) {
674            SecurityPolicy.getInstance(context).clearAccountHoldFlags();
675        }
676    }
677}
678