StorageManager.java revision d95d3bfb2b28a4f21f3fdcd740160c9a61eb0363
1/*
2 * Copyright (C) 2008 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 android.os.storage;
18
19import static android.net.TrafficStats.MB_IN_BYTES;
20
21import android.annotation.NonNull;
22import android.annotation.Nullable;
23import android.content.ContentResolver;
24import android.content.Context;
25import android.os.Environment;
26import android.os.FileUtils;
27import android.os.Handler;
28import android.os.Looper;
29import android.os.Message;
30import android.os.RemoteException;
31import android.os.ServiceManager;
32import android.provider.Settings;
33import android.text.TextUtils;
34import android.util.Log;
35import android.util.SparseArray;
36
37import com.android.internal.os.SomeArgs;
38import com.android.internal.util.Preconditions;
39
40import java.io.File;
41import java.io.IOException;
42import java.lang.ref.WeakReference;
43import java.util.ArrayList;
44import java.util.Arrays;
45import java.util.Iterator;
46import java.util.List;
47import java.util.Objects;
48import java.util.concurrent.atomic.AtomicInteger;
49
50/**
51 * StorageManager is the interface to the systems storage service. The storage
52 * manager handles storage-related items such as Opaque Binary Blobs (OBBs).
53 * <p>
54 * OBBs contain a filesystem that maybe be encrypted on disk and mounted
55 * on-demand from an application. OBBs are a good way of providing large amounts
56 * of binary assets without packaging them into APKs as they may be multiple
57 * gigabytes in size. However, due to their size, they're most likely stored in
58 * a shared storage pool accessible from all programs. The system does not
59 * guarantee the security of the OBB file itself: if any program modifies the
60 * OBB, there is no guarantee that a read from that OBB will produce the
61 * expected output.
62 * <p>
63 * Get an instance of this class by calling
64 * {@link android.content.Context#getSystemService(java.lang.String)} with an
65 * argument of {@link android.content.Context#STORAGE_SERVICE}.
66 */
67public class StorageManager {
68    private static final String TAG = "StorageManager";
69
70    /** {@hide} */
71    public static final String PROP_PRIMARY_PHYSICAL = "ro.vold.primary_physical";
72
73    /** {@hide} */
74    public static final int FLAG_ALL_METADATA = 1 << 0;
75
76    private final Context mContext;
77    private final ContentResolver mResolver;
78
79    private final IMountService mMountService;
80    private final Looper mLooper;
81    private final AtomicInteger mNextNonce = new AtomicInteger(0);
82
83    private final ArrayList<StorageEventListenerDelegate> mDelegates = new ArrayList<>();
84
85    private static class StorageEventListenerDelegate extends IMountServiceListener.Stub implements
86            Handler.Callback {
87        private static final int MSG_STORAGE_STATE_CHANGED = 1;
88        private static final int MSG_VOLUME_STATE_CHANGED = 2;
89        private static final int MSG_VOLUME_METADATA_CHANGED = 3;
90
91        final StorageEventListener mCallback;
92        final Handler mHandler;
93
94        public StorageEventListenerDelegate(StorageEventListener callback, Looper looper) {
95            mCallback = callback;
96            mHandler = new Handler(looper, this);
97        }
98
99        @Override
100        public boolean handleMessage(Message msg) {
101            final SomeArgs args = (SomeArgs) msg.obj;
102            switch (msg.what) {
103                case MSG_STORAGE_STATE_CHANGED:
104                    mCallback.onStorageStateChanged((String) args.arg1, (String) args.arg2,
105                            (String) args.arg3);
106                    args.recycle();
107                    return true;
108                case MSG_VOLUME_STATE_CHANGED:
109                    mCallback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3);
110                    args.recycle();
111                    return true;
112                case MSG_VOLUME_METADATA_CHANGED:
113                    mCallback.onVolumeMetadataChanged((VolumeInfo) args.arg1);
114                    args.recycle();
115                    return true;
116            }
117            args.recycle();
118            return false;
119        }
120
121        @Override
122        public void onUsbMassStorageConnectionChanged(boolean connected) throws RemoteException {
123            // Ignored
124        }
125
126        @Override
127        public void onStorageStateChanged(String path, String oldState, String newState) {
128            final SomeArgs args = SomeArgs.obtain();
129            args.arg1 = path;
130            args.arg2 = oldState;
131            args.arg3 = newState;
132            mHandler.obtainMessage(MSG_STORAGE_STATE_CHANGED, args).sendToTarget();
133        }
134
135        @Override
136        public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
137            final SomeArgs args = SomeArgs.obtain();
138            args.arg1 = vol;
139            args.argi2 = oldState;
140            args.argi3 = newState;
141            mHandler.obtainMessage(MSG_VOLUME_STATE_CHANGED, args).sendToTarget();
142        }
143
144        @Override
145        public void onVolumeMetadataChanged(VolumeInfo vol) {
146            final SomeArgs args = SomeArgs.obtain();
147            args.arg1 = vol;
148            mHandler.obtainMessage(MSG_VOLUME_METADATA_CHANGED, args).sendToTarget();
149        }
150    }
151
152    /**
153     * Binder listener for OBB action results.
154     */
155    private final ObbActionListener mObbActionListener = new ObbActionListener();
156
157    private class ObbActionListener extends IObbActionListener.Stub {
158        @SuppressWarnings("hiding")
159        private SparseArray<ObbListenerDelegate> mListeners = new SparseArray<ObbListenerDelegate>();
160
161        @Override
162        public void onObbResult(String filename, int nonce, int status) {
163            final ObbListenerDelegate delegate;
164            synchronized (mListeners) {
165                delegate = mListeners.get(nonce);
166                if (delegate != null) {
167                    mListeners.remove(nonce);
168                }
169            }
170
171            if (delegate != null) {
172                delegate.sendObbStateChanged(filename, status);
173            }
174        }
175
176        public int addListener(OnObbStateChangeListener listener) {
177            final ObbListenerDelegate delegate = new ObbListenerDelegate(listener);
178
179            synchronized (mListeners) {
180                mListeners.put(delegate.nonce, delegate);
181            }
182
183            return delegate.nonce;
184        }
185    }
186
187    private int getNextNonce() {
188        return mNextNonce.getAndIncrement();
189    }
190
191    /**
192     * Private class containing sender and receiver code for StorageEvents.
193     */
194    private class ObbListenerDelegate {
195        private final WeakReference<OnObbStateChangeListener> mObbEventListenerRef;
196        private final Handler mHandler;
197
198        private final int nonce;
199
200        ObbListenerDelegate(OnObbStateChangeListener listener) {
201            nonce = getNextNonce();
202            mObbEventListenerRef = new WeakReference<OnObbStateChangeListener>(listener);
203            mHandler = new Handler(mLooper) {
204                @Override
205                public void handleMessage(Message msg) {
206                    final OnObbStateChangeListener changeListener = getListener();
207                    if (changeListener == null) {
208                        return;
209                    }
210
211                    changeListener.onObbStateChange((String) msg.obj, msg.arg1);
212                }
213            };
214        }
215
216        OnObbStateChangeListener getListener() {
217            if (mObbEventListenerRef == null) {
218                return null;
219            }
220            return mObbEventListenerRef.get();
221        }
222
223        void sendObbStateChanged(String path, int state) {
224            mHandler.obtainMessage(0, state, 0, path).sendToTarget();
225        }
226    }
227
228    /** {@hide} */
229    public static StorageManager from(Context context) {
230        return (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
231    }
232
233    /**
234     * Constructs a StorageManager object through which an application can
235     * can communicate with the systems mount service.
236     *
237     * @param tgtLooper The {@link android.os.Looper} which events will be received on.
238     *
239     * <p>Applications can get instance of this class by calling
240     * {@link android.content.Context#getSystemService(java.lang.String)} with an argument
241     * of {@link android.content.Context#STORAGE_SERVICE}.
242     *
243     * @hide
244     */
245    public StorageManager(Context context, Looper looper) {
246        mContext = context;
247        mResolver = context.getContentResolver();
248        mLooper = looper;
249        mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
250        if (mMountService == null) {
251            throw new IllegalStateException("Failed to find running mount service");
252        }
253    }
254
255    /**
256     * Registers a {@link android.os.storage.StorageEventListener StorageEventListener}.
257     *
258     * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
259     *
260     * @hide
261     */
262    public void registerListener(StorageEventListener listener) {
263        synchronized (mDelegates) {
264            final StorageEventListenerDelegate delegate = new StorageEventListenerDelegate(listener,
265                    mLooper);
266            try {
267                mMountService.registerListener(delegate);
268            } catch (RemoteException e) {
269                throw e.rethrowAsRuntimeException();
270            }
271            mDelegates.add(delegate);
272        }
273    }
274
275    /**
276     * Unregisters a {@link android.os.storage.StorageEventListener StorageEventListener}.
277     *
278     * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
279     *
280     * @hide
281     */
282    public void unregisterListener(StorageEventListener listener) {
283        synchronized (mDelegates) {
284            for (Iterator<StorageEventListenerDelegate> i = mDelegates.iterator(); i.hasNext();) {
285                final StorageEventListenerDelegate delegate = i.next();
286                if (delegate.mCallback == listener) {
287                    try {
288                        mMountService.unregisterListener(delegate);
289                    } catch (RemoteException e) {
290                        throw e.rethrowAsRuntimeException();
291                    }
292                    i.remove();
293                }
294            }
295        }
296    }
297
298    /**
299     * Enables USB Mass Storage (UMS) on the device.
300     *
301     * @hide
302     */
303    @Deprecated
304    public void enableUsbMassStorage() {
305    }
306
307    /**
308     * Disables USB Mass Storage (UMS) on the device.
309     *
310     * @hide
311     */
312    @Deprecated
313    public void disableUsbMassStorage() {
314    }
315
316    /**
317     * Query if a USB Mass Storage (UMS) host is connected.
318     * @return true if UMS host is connected.
319     *
320     * @hide
321     */
322    @Deprecated
323    public boolean isUsbMassStorageConnected() {
324        return false;
325    }
326
327    /**
328     * Query if a USB Mass Storage (UMS) is enabled on the device.
329     * @return true if UMS host is enabled.
330     *
331     * @hide
332     */
333    @Deprecated
334    public boolean isUsbMassStorageEnabled() {
335        return false;
336    }
337
338    /**
339     * Mount an Opaque Binary Blob (OBB) file. If a <code>key</code> is
340     * specified, it is supplied to the mounting process to be used in any
341     * encryption used in the OBB.
342     * <p>
343     * The OBB will remain mounted for as long as the StorageManager reference
344     * is held by the application. As soon as this reference is lost, the OBBs
345     * in use will be unmounted. The {@link OnObbStateChangeListener} registered
346     * with this call will receive the success or failure of this operation.
347     * <p>
348     * <em>Note:</em> you can only mount OBB files for which the OBB tag on the
349     * file matches a package ID that is owned by the calling program's UID.
350     * That is, shared UID applications can attempt to mount any other
351     * application's OBB that shares its UID.
352     *
353     * @param rawPath the path to the OBB file
354     * @param key secret used to encrypt the OBB; may be <code>null</code> if no
355     *            encryption was used on the OBB.
356     * @param listener will receive the success or failure of the operation
357     * @return whether the mount call was successfully queued or not
358     */
359    public boolean mountObb(String rawPath, String key, OnObbStateChangeListener listener) {
360        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
361        Preconditions.checkNotNull(listener, "listener cannot be null");
362
363        try {
364            final String canonicalPath = new File(rawPath).getCanonicalPath();
365            final int nonce = mObbActionListener.addListener(listener);
366            mMountService.mountObb(rawPath, canonicalPath, key, mObbActionListener, nonce);
367            return true;
368        } catch (IOException e) {
369            throw new IllegalArgumentException("Failed to resolve path: " + rawPath, e);
370        } catch (RemoteException e) {
371            Log.e(TAG, "Failed to mount OBB", e);
372        }
373
374        return false;
375    }
376
377    /**
378     * Unmount an Opaque Binary Blob (OBB) file asynchronously. If the
379     * <code>force</code> flag is true, it will kill any application needed to
380     * unmount the given OBB (even the calling application).
381     * <p>
382     * The {@link OnObbStateChangeListener} registered with this call will
383     * receive the success or failure of this operation.
384     * <p>
385     * <em>Note:</em> you can only mount OBB files for which the OBB tag on the
386     * file matches a package ID that is owned by the calling program's UID.
387     * That is, shared UID applications can obtain access to any other
388     * application's OBB that shares its UID.
389     * <p>
390     *
391     * @param rawPath path to the OBB file
392     * @param force whether to kill any programs using this in order to unmount
393     *            it
394     * @param listener will receive the success or failure of the operation
395     * @return whether the unmount call was successfully queued or not
396     */
397    public boolean unmountObb(String rawPath, boolean force, OnObbStateChangeListener listener) {
398        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
399        Preconditions.checkNotNull(listener, "listener cannot be null");
400
401        try {
402            final int nonce = mObbActionListener.addListener(listener);
403            mMountService.unmountObb(rawPath, force, mObbActionListener, nonce);
404            return true;
405        } catch (RemoteException e) {
406            Log.e(TAG, "Failed to mount OBB", e);
407        }
408
409        return false;
410    }
411
412    /**
413     * Check whether an Opaque Binary Blob (OBB) is mounted or not.
414     *
415     * @param rawPath path to OBB image
416     * @return true if OBB is mounted; false if not mounted or on error
417     */
418    public boolean isObbMounted(String rawPath) {
419        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
420
421        try {
422            return mMountService.isObbMounted(rawPath);
423        } catch (RemoteException e) {
424            Log.e(TAG, "Failed to check if OBB is mounted", e);
425        }
426
427        return false;
428    }
429
430    /**
431     * Check the mounted path of an Opaque Binary Blob (OBB) file. This will
432     * give you the path to where you can obtain access to the internals of the
433     * OBB.
434     *
435     * @param rawPath path to OBB image
436     * @return absolute path to mounted OBB image data or <code>null</code> if
437     *         not mounted or exception encountered trying to read status
438     */
439    public String getMountedObbPath(String rawPath) {
440        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
441
442        try {
443            return mMountService.getMountedObbPath(rawPath);
444        } catch (RemoteException e) {
445            Log.e(TAG, "Failed to find mounted path for OBB", e);
446        }
447
448        return null;
449    }
450
451    /** {@hide} */
452    public @NonNull List<DiskInfo> getDisks() {
453        try {
454            return Arrays.asList(mMountService.getDisks());
455        } catch (RemoteException e) {
456            throw e.rethrowAsRuntimeException();
457        }
458    }
459
460    /** {@hide} */
461    public @Nullable DiskInfo findDiskById(String id) {
462        Preconditions.checkNotNull(id);
463        // TODO; go directly to service to make this faster
464        for (DiskInfo disk : getDisks()) {
465            if (Objects.equals(disk.id, id)) {
466                return disk;
467            }
468        }
469        return null;
470    }
471
472    /** {@hide} */
473    public @Nullable VolumeInfo findVolumeById(String id) {
474        Preconditions.checkNotNull(id);
475        // TODO; go directly to service to make this faster
476        for (VolumeInfo vol : getVolumes()) {
477            if (Objects.equals(vol.id, id)) {
478                return vol;
479            }
480        }
481        return null;
482    }
483
484    /** {@hide} */
485    public @Nullable VolumeInfo findVolumeByUuid(String fsUuid) {
486        Preconditions.checkNotNull(fsUuid);
487        // TODO; go directly to service to make this faster
488        for (VolumeInfo vol : getVolumes()) {
489            if (Objects.equals(vol.fsUuid, fsUuid)) {
490                return vol;
491            }
492        }
493        return null;
494    }
495
496    /** {@hide} */
497    public @NonNull List<VolumeInfo> getVolumes() {
498        return getVolumes(0);
499    }
500
501    /** {@hide} */
502    public @NonNull List<VolumeInfo> getVolumes(int flags) {
503        try {
504            return Arrays.asList(mMountService.getVolumes(flags));
505        } catch (RemoteException e) {
506            throw e.rethrowAsRuntimeException();
507        }
508    }
509
510    /** {@hide} */
511    public @Nullable String getBestVolumeDescription(VolumeInfo vol) {
512        String descrip = vol.getDescription();
513
514        if (vol.diskId != null) {
515            final DiskInfo disk = findDiskById(vol.diskId);
516            if (disk != null && TextUtils.isEmpty(descrip)) {
517                descrip = disk.getDescription();
518            }
519        }
520
521        return descrip;
522    }
523
524    /** {@hide} */
525    public void mount(String volId) {
526        try {
527            mMountService.mount(volId);
528        } catch (RemoteException e) {
529            throw e.rethrowAsRuntimeException();
530        }
531    }
532
533    /** {@hide} */
534    public void unmount(String volId) {
535        try {
536            mMountService.unmount(volId);
537        } catch (RemoteException e) {
538            throw e.rethrowAsRuntimeException();
539        }
540    }
541
542    /** {@hide} */
543    public void format(String volId) {
544        try {
545            mMountService.format(volId);
546        } catch (RemoteException e) {
547            throw e.rethrowAsRuntimeException();
548        }
549    }
550
551    /** {@hide} */
552    public void partitionPublic(String diskId) {
553        try {
554            mMountService.partitionPublic(diskId);
555        } catch (RemoteException e) {
556            throw e.rethrowAsRuntimeException();
557        }
558    }
559
560    /** {@hide} */
561    public void partitionPrivate(String diskId) {
562        try {
563            mMountService.partitionPrivate(diskId);
564        } catch (RemoteException e) {
565            throw e.rethrowAsRuntimeException();
566        }
567    }
568
569    /** {@hide} */
570    public void partitionMixed(String diskId, int ratio) {
571        try {
572            mMountService.partitionMixed(diskId, ratio);
573        } catch (RemoteException e) {
574            throw e.rethrowAsRuntimeException();
575        }
576    }
577
578    /** {@hide} */
579    public void setVolumeNickname(String volId, String nickname) {
580        try {
581            mMountService.setVolumeNickname(volId, nickname);
582        } catch (RemoteException e) {
583            throw e.rethrowAsRuntimeException();
584        }
585    }
586
587    /** {@hide} */
588    public void setVolumeInited(String volId, boolean inited) {
589        try {
590            mMountService.setVolumeUserFlags(volId, inited ? VolumeInfo.USER_FLAG_INITED : 0,
591                    VolumeInfo.USER_FLAG_INITED);
592        } catch (RemoteException e) {
593            throw e.rethrowAsRuntimeException();
594        }
595    }
596
597    /** {@hide} */
598    public void setVolumeSnoozed(String volId, boolean snoozed) {
599        try {
600            mMountService.setVolumeUserFlags(volId, snoozed ? VolumeInfo.USER_FLAG_SNOOZED : 0,
601                    VolumeInfo.USER_FLAG_SNOOZED);
602        } catch (RemoteException e) {
603            throw e.rethrowAsRuntimeException();
604        }
605    }
606
607    /** {@hide} */
608    public @Nullable StorageVolume getStorageVolume(File file) {
609        return getStorageVolume(getVolumeList(), file);
610    }
611
612    /** {@hide} */
613    public static @Nullable StorageVolume getStorageVolume(File file, int userId) {
614        return getStorageVolume(getVolumeList(userId), file);
615    }
616
617    /** {@hide} */
618    private static @Nullable StorageVolume getStorageVolume(StorageVolume[] volumes, File file) {
619        File canonicalFile = null;
620        try {
621            canonicalFile = file.getCanonicalFile();
622        } catch (IOException ignored) {
623            canonicalFile = null;
624        }
625        for (StorageVolume volume : volumes) {
626            if (volume.getPathFile().equals(file)) {
627                return volume;
628            }
629            if (FileUtils.contains(volume.getPathFile(), canonicalFile)) {
630                return volume;
631            }
632        }
633        return null;
634    }
635
636    /**
637     * Gets the state of a volume via its mountpoint.
638     * @hide
639     */
640    @Deprecated
641    public @NonNull String getVolumeState(String mountPoint) {
642        final StorageVolume vol = getStorageVolume(new File(mountPoint));
643        if (vol != null) {
644            return vol.getState();
645        } else {
646            return Environment.MEDIA_UNKNOWN;
647        }
648    }
649
650    /** {@hide} */
651    public @NonNull StorageVolume[] getVolumeList() {
652        return getVolumeList(mContext.getUserId());
653    }
654
655    /** {@hide} */
656    public static @NonNull StorageVolume[] getVolumeList(int userId) {
657        final IMountService mountService = IMountService.Stub.asInterface(
658                ServiceManager.getService("mount"));
659        try {
660            return mountService.getVolumeList(userId);
661        } catch (RemoteException e) {
662            throw e.rethrowAsRuntimeException();
663        }
664    }
665
666    /**
667     * Returns list of paths for all mountable volumes.
668     * @hide
669     */
670    @Deprecated
671    public @NonNull String[] getVolumePaths() {
672        StorageVolume[] volumes = getVolumeList();
673        int count = volumes.length;
674        String[] paths = new String[count];
675        for (int i = 0; i < count; i++) {
676            paths[i] = volumes[i].getPath();
677        }
678        return paths;
679    }
680
681    /** {@hide} */
682    public @NonNull StorageVolume getPrimaryVolume() {
683        return getPrimaryVolume(getVolumeList());
684    }
685
686    /** {@hide} */
687    public static @NonNull StorageVolume getPrimaryVolume(StorageVolume[] volumes) {
688        for (StorageVolume volume : volumes) {
689            if (volume.isPrimary()) {
690                return volume;
691            }
692        }
693        throw new IllegalStateException("Missing primary storage");
694    }
695
696    /** {@hide} */
697    private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
698    private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500 * MB_IN_BYTES;
699    private static final long DEFAULT_FULL_THRESHOLD_BYTES = MB_IN_BYTES;
700
701    /**
702     * Return the number of available bytes until the given path is considered
703     * running low on storage.
704     *
705     * @hide
706     */
707    public long getStorageBytesUntilLow(File path) {
708        return path.getUsableSpace() - getStorageFullBytes(path);
709    }
710
711    /**
712     * Return the number of available bytes at which the given path is
713     * considered running low on storage.
714     *
715     * @hide
716     */
717    public long getStorageLowBytes(File path) {
718        final long lowPercent = Settings.Global.getInt(mResolver,
719                Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE);
720        final long lowBytes = (path.getTotalSpace() * lowPercent) / 100;
721
722        final long maxLowBytes = Settings.Global.getLong(mResolver,
723                Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES, DEFAULT_THRESHOLD_MAX_BYTES);
724
725        return Math.min(lowBytes, maxLowBytes);
726    }
727
728    /**
729     * Return the number of available bytes at which the given path is
730     * considered full.
731     *
732     * @hide
733     */
734    public long getStorageFullBytes(File path) {
735        return Settings.Global.getLong(mResolver, Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
736                DEFAULT_FULL_THRESHOLD_BYTES);
737    }
738
739    /// Consts to match the password types in cryptfs.h
740    /** @hide */
741    public static final int CRYPT_TYPE_PASSWORD = 0;
742    /** @hide */
743    public static final int CRYPT_TYPE_DEFAULT = 1;
744    /** @hide */
745    public static final int CRYPT_TYPE_PATTERN = 2;
746    /** @hide */
747    public static final int CRYPT_TYPE_PIN = 3;
748
749    // Constants for the data available via MountService.getField.
750    /** @hide */
751    public static final String SYSTEM_LOCALE_KEY = "SystemLocale";
752    /** @hide */
753    public static final String OWNER_INFO_KEY = "OwnerInfo";
754    /** @hide */
755    public static final String PATTERN_VISIBLE_KEY = "PatternVisible";
756}
757