Shared.java revision b0cbd6b0b18ea740524808b5af1f99aff628ee3b
17a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten/*
27a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten * Copyright (C) 2015 The Android Open Source Project
37a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten *
47a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten * Licensed under the Apache License, Version 2.0 (the "License");
57a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten * you may not use this file except in compliance with the License.
67a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten * You may obtain a copy of the License at
77a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten *
87a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten *      http://www.apache.org/licenses/LICENSE-2.0
97a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten *
107a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten * Unless required by applicable law or agreed to in writing, software
117a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten * distributed under the License is distributed on an "AS IS" BASIS,
127a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
137a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten * See the License for the specific language governing permissions and
147a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten * limitations under the License.
157a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten */
167a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten
175dacc932cd1084e0cd746afe0a4d7e035560113cGlenn Kastenpackage com.android.documentsui.base;
187a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten
19c6853892c94800e72c0bd676d5d2136d48cea76eGlenn Kastenimport android.annotation.PluralsRes;
2034ea74827da9a7401d779f7dab9bc63b8253baa4Glenn Kastenimport android.app.Activity;
2101e9f5fa4698856f92bcfd88188ee4c8397b22dbGlenn Kastenimport android.app.AlertDialog;
22c6853892c94800e72c0bd676d5d2136d48cea76eGlenn Kastenimport android.content.Context;
2334ea74827da9a7401d779f7dab9bc63b8253baa4Glenn Kastenimport android.content.Intent;
2401e9f5fa4698856f92bcfd88188ee4c8397b22dbGlenn Kastenimport android.content.pm.ApplicationInfo;
255dacc932cd1084e0cd746afe0a4d7e035560113cGlenn Kastenimport android.content.pm.PackageManager.NameNotFoundException;
267a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kastenimport android.content.res.Configuration;
277a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kastenimport android.net.Uri;
287a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kastenimport android.os.Looper;
297a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kastenimport android.provider.DocumentsContract;
30369f3138f19f7102bf0f98b890ab84c8df633a93Glenn Kastenimport android.text.TextUtils;
31369f3138f19f7102bf0f98b890ab84c8df633a93Glenn Kastenimport android.text.format.DateUtils;
327a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kastenimport android.text.format.Time;
337a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kastenimport android.util.Log;
34369f3138f19f7102bf0f98b890ab84c8df633a93Glenn Kastenimport android.view.WindowManager;
35369f3138f19f7102bf0f98b890ab84c8df633a93Glenn Kasten
367a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kastenimport com.android.documentsui.R;
377a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten
387a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kastenimport java.io.PrintWriter;
397a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kastenimport java.io.StringWriter;
407a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kastenimport java.text.Collator;
4134ea74827da9a7401d779f7dab9bc63b8253baa4Glenn Kastenimport java.util.ArrayList;
4234ea74827da9a7401d779f7dab9bc63b8253baa4Glenn Kastenimport java.util.List;
437a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten
44369f3138f19f7102bf0f98b890ab84c8df633a93Glenn Kastenimport javax.annotation.Nullable;
45369f3138f19f7102bf0f98b890ab84c8df633a93Glenn Kasten
46369f3138f19f7102bf0f98b890ab84c8df633a93Glenn Kasten/** @hide */
477a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kastenpublic final class Shared {
487a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten
497a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten    public static final String TAG = "Documents";
507a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten
517a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten    public static final boolean DEBUG = true;
527a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten    public static final boolean VERBOSE = DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
537a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten
547a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten    public static final boolean ENABLE_OMC_API_FEATURES = true;
557a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten
567a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten    /** Intent action name to pick a copy destination. */
577a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten    public static final String ACTION_PICK_COPY_DESTINATION =
587a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten            "com.android.documentsui.PICK_COPY_DESTINATION";
597a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten
607a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten    /**
617a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten     * Extra boolean flag for {@link #ACTION_PICK_COPY_DESTINATION}, which
627a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten     * specifies if the destination directory needs to create new directory or not.
637a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten     */
647a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten    public static final String EXTRA_DIRECTORY_COPY = "com.android.documentsui.DIRECTORY_COPY";
657a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten
667a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten    /**
677a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten     * Extra flag used to store the current stack so user opens in right spot.
687a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten     */
697a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten    public static final String EXTRA_STACK = "com.android.documentsui.STACK";
707a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten
717a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten    /**
727a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten     * Extra flag used to store query of type String in the bundle.
737a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten     */
747a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten    public static final String EXTRA_QUERY = "query";
757a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten
767a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten    /**
777a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten     * Extra flag used to store state of type State in the bundle.
787a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten     */
797a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten    public static final String EXTRA_STATE = "state";
807a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten
817a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten    /**
827a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten     * Extra flag used to store type of DirectoryFragment's type ResultType type in the bundle.
837a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten     */
847a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten    public static final String EXTRA_TYPE = "type";
857a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten
867a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten    /**
877a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten     * Extra flag used to store root of type RootInfo in the bundle.
887a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten     */
897a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten    public static final String EXTRA_ROOT = "root";
9001e9f5fa4698856f92bcfd88188ee4c8397b22dbGlenn Kasten
9101e9f5fa4698856f92bcfd88188ee4c8397b22dbGlenn Kasten    /**
9201e9f5fa4698856f92bcfd88188ee4c8397b22dbGlenn Kasten     * Extra flag used to store document of DocumentInfo type in the bundle.
9301e9f5fa4698856f92bcfd88188ee4c8397b22dbGlenn Kasten     */
9434ea74827da9a7401d779f7dab9bc63b8253baa4Glenn Kasten    public static final String EXTRA_DOC = "document";
9534ea74827da9a7401d779f7dab9bc63b8253baa4Glenn Kasten
9601e9f5fa4698856f92bcfd88188ee4c8397b22dbGlenn Kasten    /**
9701e9f5fa4698856f92bcfd88188ee4c8397b22dbGlenn Kasten     * Extra flag used to store DirectoryFragment's selection of Selection type in the bundle.
9801e9f5fa4698856f92bcfd88188ee4c8397b22dbGlenn Kasten     */
9901e9f5fa4698856f92bcfd88188ee4c8397b22dbGlenn Kasten    public static final String EXTRA_SELECTION = "selection";
10001e9f5fa4698856f92bcfd88188ee4c8397b22dbGlenn Kasten
101f8b3141926967ba37d315cc8d3956d7214958e6bPaul McLean    /**
10234ea74827da9a7401d779f7dab9bc63b8253baa4Glenn Kasten     * Extra flag used to store DirectoryFragment's search mode of boolean type in the bundle.
103f8b3141926967ba37d315cc8d3956d7214958e6bPaul McLean     */
104f8b3141926967ba37d315cc8d3956d7214958e6bPaul McLean    public static final String EXTRA_SEARCH_MODE = "searchMode";
105f8b3141926967ba37d315cc8d3956d7214958e6bPaul McLean
10601e9f5fa4698856f92bcfd88188ee4c8397b22dbGlenn Kasten    /**
1077a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten     * Extra flag used to store DirectoryFragment's ignore state of boolean type in the bundle.
1087a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten     */
1097126c25d7c037e5086216cf540ecf40779c3585aGlenn Kasten    public static final String EXTRA_IGNORE_STATE = "ignoreState";
110369f3138f19f7102bf0f98b890ab84c8df633a93Glenn Kasten
111369f3138f19f7102bf0f98b890ab84c8df633a93Glenn Kasten    /**
1125dacc932cd1084e0cd746afe0a4d7e035560113cGlenn Kasten     * Extra for an Intent for enabling performance benchmark. Used only by tests.
1137a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten     */
1147a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten    public static final String EXTRA_BENCHMARK = "com.android.documentsui.benchmark";
1157a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten
1167a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten    /**
1177a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten     * Maximum number of items in a Binder transaction packet.
1187a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten     */
1197a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten    public static final int MAX_DOCS_IN_INTENT = 500;
1207a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten
1217a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten    /**
1227a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten     * Animation duration of checkbox in directory list/grid in millis.
1237a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten     */
1247a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten    public static final int CHECK_ANIMATION_DURATION = 100;
1257a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten
1267a79f519d89eb0e1a5b3f4005484b16d6854d7e2Glenn Kasten    private static final Collator sCollator;
1274b65ef9efdf5aba01bea89d8cdd64f500560a28dGlenn Kasten
1287126c25d7c037e5086216cf540ecf40779c3585aGlenn Kasten    static {
129369f3138f19f7102bf0f98b890ab84c8df633a93Glenn Kasten        sCollator = Collator.getInstance();
130369f3138f19f7102bf0f98b890ab84c8df633a93Glenn Kasten        sCollator.setStrength(Collator.SECONDARY);
131369f3138f19f7102bf0f98b890ab84c8df633a93Glenn Kasten    }
132369f3138f19f7102bf0f98b890ab84c8df633a93Glenn Kasten
1335dacc932cd1084e0cd746afe0a4d7e035560113cGlenn Kasten    /**
1344b65ef9efdf5aba01bea89d8cdd64f500560a28dGlenn Kasten     * @deprecated use {@ link MessageBuilder#getQuantityString}
1354b65ef9efdf5aba01bea89d8cdd64f500560a28dGlenn Kasten     */
1364b65ef9efdf5aba01bea89d8cdd64f500560a28dGlenn Kasten    @Deprecated
1374b65ef9efdf5aba01bea89d8cdd64f500560a28dGlenn Kasten    public static final String getQuantityString(Context context, @PluralsRes int resourceId, int quantity) {
1384b65ef9efdf5aba01bea89d8cdd64f500560a28dGlenn Kasten        return context.getResources().getQuantityString(resourceId, quantity, quantity);
1395dacc932cd1084e0cd746afe0a4d7e035560113cGlenn Kasten    }
1404b65ef9efdf5aba01bea89d8cdd64f500560a28dGlenn Kasten
1414b65ef9efdf5aba01bea89d8cdd64f500560a28dGlenn Kasten    public static String formatTime(Context context, long when) {
1424b65ef9efdf5aba01bea89d8cdd64f500560a28dGlenn Kasten        // TODO: DateUtils should make this easier
143768edbc2a8e7f197548cf1141689dd237d19c455Glenn Kasten        Time then = new Time();
144768edbc2a8e7f197548cf1141689dd237d19c455Glenn Kasten        then.set(when);
145c2b9d79c4b59caff965076f445f5a735a360b084Glenn Kasten        Time now = new Time();
146c2b9d79c4b59caff965076f445f5a735a360b084Glenn Kasten        now.setToNow();
147c2b9d79c4b59caff965076f445f5a735a360b084Glenn Kasten
148c2b9d79c4b59caff965076f445f5a735a360b084Glenn Kasten        int flags = DateUtils.FORMAT_NO_NOON | DateUtils.FORMAT_NO_MIDNIGHT
149c2b9d79c4b59caff965076f445f5a735a360b084Glenn Kasten                | DateUtils.FORMAT_ABBREV_ALL;
150c2b9d79c4b59caff965076f445f5a735a360b084Glenn Kasten
151c2b9d79c4b59caff965076f445f5a735a360b084Glenn Kasten        if (then.year != now.year) {
152c2b9d79c4b59caff965076f445f5a735a360b084Glenn Kasten            flags |= DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE;
153c2b9d79c4b59caff965076f445f5a735a360b084Glenn Kasten        } else if (then.yearDay != now.yearDay) {
154c2b9d79c4b59caff965076f445f5a735a360b084Glenn Kasten            flags |= DateUtils.FORMAT_SHOW_DATE;
155c2b9d79c4b59caff965076f445f5a735a360b084Glenn Kasten        } else {
156c2b9d79c4b59caff965076f445f5a735a360b084Glenn Kasten            flags |= DateUtils.FORMAT_SHOW_TIME;
157c2b9d79c4b59caff965076f445f5a735a360b084Glenn Kasten        }
158c2b9d79c4b59caff965076f445f5a735a360b084Glenn Kasten
159c2b9d79c4b59caff965076f445f5a735a360b084Glenn Kasten        return DateUtils.formatDateTime(context, when, flags);
160c2b9d79c4b59caff965076f445f5a735a360b084Glenn Kasten    }
161c2b9d79c4b59caff965076f445f5a735a360b084Glenn Kasten
162c2b9d79c4b59caff965076f445f5a735a360b084Glenn Kasten    /**
163     * A convenient way to transform any list into a (parcelable) ArrayList.
164     * Uses cast if possible, else creates a new list with entries from {@code list}.
165     */
166    public static <T> ArrayList<T> asArrayList(List<T> list) {
167        return list instanceof ArrayList
168            ? (ArrayList<T>) list
169            : new ArrayList<>(list);
170    }
171
172    /**
173     * Returns a condensed stacktrace in String format, separated by \n.
174     */
175    public static String getStackTrace(Exception e) {
176        StringWriter sw = new StringWriter();
177        PrintWriter pw = new PrintWriter(sw);
178        e.printStackTrace(pw);
179        return sw.toString();
180    }
181
182    /**
183     * Compare two strings against each other using system default collator in a
184     * case-insensitive mode. Clusters strings prefixed with {@link DIR_PREFIX}
185     * before other items.
186     */
187    public static int compareToIgnoreCaseNullable(String lhs, String rhs) {
188        final boolean leftEmpty = TextUtils.isEmpty(lhs);
189        final boolean rightEmpty = TextUtils.isEmpty(rhs);
190
191        if (leftEmpty && rightEmpty) return 0;
192        if (leftEmpty) return -1;
193        if (rightEmpty) return 1;
194
195        return sCollator.compare(lhs, rhs);
196    }
197
198    /**
199     * Returns the calling package, possibly overridden by EXTRA_PACKAGE_NAME.
200     * @param activity
201     * @return
202     */
203    public static String getCallingPackageName(Activity activity) {
204        String callingPackage = activity.getCallingPackage();
205        // System apps can set the calling package name using an extra.
206        try {
207            ApplicationInfo info =
208                    activity.getPackageManager().getApplicationInfo(callingPackage, 0);
209            if (info.isSystemApp() || info.isUpdatedSystemApp()) {
210                final String extra = activity.getIntent().getStringExtra(
211                        DocumentsContract.EXTRA_PACKAGE_NAME);
212                if (extra != null && !TextUtils.isEmpty(extra)) {
213                    callingPackage = extra;
214                }
215            }
216        } catch (NameNotFoundException e) {
217            // Couldn't lookup calling package info. This isn't really
218            // gonna happen, given that we're getting the name of the
219            // calling package from trusty old Activity.getCallingPackage.
220            // For that reason, we ignore this exception.
221        }
222        return callingPackage;
223    }
224
225    /**
226     * Returns the default directory to be presented after starting the activity.
227     * Method can be overridden if the change of the behavior of the the child activity is needed.
228     */
229    public static Uri getDefaultRootUri(Activity activity) {
230        return shouldShowDocumentsRoot(activity)
231                ? DocumentsContract.buildHomeUri()
232                : DocumentsContract.buildRootUri(
233                        Providers.AUTHORITY_DOWNLOADS, Providers.ROOT_ID_DOWNLOADS);
234    }
235
236    public static boolean isHardwareKeyboardAvailable(Context context) {
237        return context.getResources().getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS;
238    }
239
240    public static void ensureKeyboardPresent(Context context, AlertDialog dialog) {
241        if (!isHardwareKeyboardAvailable(context)) {
242            dialog.getWindow().setSoftInputMode(
243                    WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
244        }
245    }
246
247    /**
248     * Returns true if "Documents" root should be shown.
249     */
250    public static boolean shouldShowDocumentsRoot(Context context) {
251        return context.getResources().getBoolean(R.bool.show_documents_root);
252    }
253
254    /**
255     * Returns true if compressing is enabled.
256     */
257    public static boolean isCompressingEnabled(Context context) {
258        return context.getResources().getBoolean(R.bool.enable_compressing);
259    }
260
261    /*
262     * Returns true if the local/device storage root must be visible (this also hides
263     * the option to toggle visibility in the menu.)
264     */
265    public static boolean mustShowDeviceRoot(Intent intent) {
266        return intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, false);
267    }
268
269    public static void checkMainLoop() {
270        if (Looper.getMainLooper() != Looper.myLooper()) {
271            Log.e(TAG, "Calling from non-UI thread!");
272        }
273    }
274
275    public static @Nullable <T> T findView(Activity activity, int... resources) {
276        for (int id : resources) {
277            @SuppressWarnings("unchecked")
278            T r = (T) activity.findViewById(id);
279            if (r != null) {
280                return r;
281            }
282        }
283        return null;
284    }
285}
286