| /* |
| * Copyright (C) 2015 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 com.android.documentsui.base; |
| |
| import android.annotation.PluralsRes; |
| import android.app.Activity; |
| import android.app.AlertDialog; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.content.res.Configuration; |
| import android.net.Uri; |
| import android.os.Build; |
| import android.os.Looper; |
| import android.provider.DocumentsContract; |
| import android.provider.Settings; |
| import android.text.TextUtils; |
| import android.text.format.DateUtils; |
| import android.text.format.Time; |
| import android.util.Log; |
| import android.view.View; |
| import android.view.WindowManager; |
| |
| import com.android.documentsui.R; |
| import com.android.documentsui.ui.MessageBuilder; |
| |
| import java.text.Collator; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import javax.annotation.Nullable; |
| |
| /** @hide */ |
| public final class Shared { |
| |
| public static final String TAG = "Documents"; |
| |
| public static final boolean DEBUG = Build.IS_DEBUGGABLE; |
| public static final boolean VERBOSE = DEBUG && Log.isLoggable(TAG, Log.VERBOSE); |
| |
| /** Intent action name to pick a copy destination. */ |
| public static final String ACTION_PICK_COPY_DESTINATION = |
| "com.android.documentsui.PICK_COPY_DESTINATION"; |
| |
| // These values track values declared in MediaDocumentsProvider. |
| public static final String METADATA_KEY_AUDIO = "android.media.metadata.audio"; |
| public static final String METADATA_KEY_VIDEO = "android.media.metadata.video"; |
| public static final String METADATA_VIDEO_LATITUDE = "android.media.metadata.video:latitude"; |
| public static final String METADATA_VIDEO_LONGITUTE = "android.media.metadata.video:longitude"; |
| |
| /** |
| * Extra boolean flag for {@link #ACTION_PICK_COPY_DESTINATION}, which |
| * specifies if the destination directory needs to create new directory or not. |
| */ |
| public static final String EXTRA_DIRECTORY_COPY = "com.android.documentsui.DIRECTORY_COPY"; |
| |
| /** |
| * Extra flag used to store the current stack so user opens in right spot. |
| */ |
| public static final String EXTRA_STACK = "com.android.documentsui.STACK"; |
| |
| /** |
| * Extra flag used to store query of type String in the bundle. |
| */ |
| public static final String EXTRA_QUERY = "query"; |
| |
| /** |
| * Extra flag used to store state of type State in the bundle. |
| */ |
| public static final String EXTRA_STATE = "state"; |
| |
| /** |
| * Extra flag used to store root of type RootInfo in the bundle. |
| */ |
| public static final String EXTRA_ROOT = "root"; |
| |
| /** |
| * Extra flag used to store document of DocumentInfo type in the bundle. |
| */ |
| public static final String EXTRA_DOC = "document"; |
| |
| /** |
| * Extra flag used to store DirectoryFragment's selection of Selection type in the bundle. |
| */ |
| public static final String EXTRA_SELECTION = "selection"; |
| |
| /** |
| * Extra flag used to store DirectoryFragment's ignore state of boolean type in the bundle. |
| */ |
| public static final String EXTRA_IGNORE_STATE = "ignoreState"; |
| |
| /** |
| * Extra for an Intent for enabling performance benchmark. Used only by tests. |
| */ |
| public static final String EXTRA_BENCHMARK = "com.android.documentsui.benchmark"; |
| |
| /** |
| * Extra flag used to signify to inspector that debug section can be shown. |
| */ |
| public static final String EXTRA_SHOW_DEBUG = "com.android.documentsui.SHOW_DEBUG"; |
| |
| /** |
| * Maximum number of items in a Binder transaction packet. |
| */ |
| public static final int MAX_DOCS_IN_INTENT = 500; |
| |
| /** |
| * Animation duration of checkbox in directory list/grid in millis. |
| */ |
| public static final int CHECK_ANIMATION_DURATION = 100; |
| |
| private static final Collator sCollator; |
| |
| static { |
| sCollator = Collator.getInstance(); |
| sCollator.setStrength(Collator.SECONDARY); |
| } |
| |
| /** |
| * @deprecated use {@link MessageBuilder#getQuantityString} |
| */ |
| @Deprecated |
| public static final String getQuantityString(Context context, @PluralsRes int resourceId, int quantity) { |
| return context.getResources().getQuantityString(resourceId, quantity, quantity); |
| } |
| |
| public static String formatTime(Context context, long when) { |
| // TODO: DateUtils should make this easier |
| Time then = new Time(); |
| then.set(when); |
| Time now = new Time(); |
| now.setToNow(); |
| |
| int flags = DateUtils.FORMAT_NO_NOON | DateUtils.FORMAT_NO_MIDNIGHT |
| | DateUtils.FORMAT_ABBREV_ALL; |
| |
| if (then.year != now.year) { |
| flags |= DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE; |
| } else if (then.yearDay != now.yearDay) { |
| flags |= DateUtils.FORMAT_SHOW_DATE; |
| } else { |
| flags |= DateUtils.FORMAT_SHOW_TIME; |
| } |
| |
| return DateUtils.formatDateTime(context, when, flags); |
| } |
| |
| /** |
| * A convenient way to transform any list into a (parcelable) ArrayList. |
| * Uses cast if possible, else creates a new list with entries from {@code list}. |
| */ |
| public static <T> ArrayList<T> asArrayList(List<T> list) { |
| return list instanceof ArrayList |
| ? (ArrayList<T>) list |
| : new ArrayList<>(list); |
| } |
| |
| /** |
| * Compare two strings against each other using system default collator in a |
| * case-insensitive mode. Clusters strings prefixed with {@link DIR_PREFIX} |
| * before other items. |
| */ |
| public static int compareToIgnoreCaseNullable(String lhs, String rhs) { |
| final boolean leftEmpty = TextUtils.isEmpty(lhs); |
| final boolean rightEmpty = TextUtils.isEmpty(rhs); |
| |
| if (leftEmpty && rightEmpty) return 0; |
| if (leftEmpty) return -1; |
| if (rightEmpty) return 1; |
| |
| return sCollator.compare(lhs, rhs); |
| } |
| |
| /** |
| * Returns the calling package, possibly overridden by EXTRA_PACKAGE_NAME. |
| * @param activity |
| * @return |
| */ |
| public static String getCallingPackageName(Activity activity) { |
| String callingPackage = activity.getCallingPackage(); |
| // System apps can set the calling package name using an extra. |
| try { |
| ApplicationInfo info = |
| activity.getPackageManager().getApplicationInfo(callingPackage, 0); |
| if (info.isSystemApp() || info.isUpdatedSystemApp()) { |
| final String extra = activity.getIntent().getStringExtra( |
| DocumentsContract.EXTRA_PACKAGE_NAME); |
| if (extra != null && !TextUtils.isEmpty(extra)) { |
| callingPackage = extra; |
| } |
| } |
| } catch (NameNotFoundException e) { |
| // Couldn't lookup calling package info. This isn't really |
| // gonna happen, given that we're getting the name of the |
| // calling package from trusty old Activity.getCallingPackage. |
| // For that reason, we ignore this exception. |
| } |
| return callingPackage; |
| } |
| |
| /** |
| * Returns the default directory to be presented after starting the activity. |
| * Method can be overridden if the change of the behavior of the the child activity is needed. |
| */ |
| public static Uri getDefaultRootUri(Activity activity) { |
| Uri defaultUri = Uri.parse(activity.getResources().getString(R.string.default_root_uri)); |
| |
| if (!DocumentsContract.isRootUri(activity, defaultUri)) { |
| throw new RuntimeException("Default Root URI is not a valid root URI."); |
| } |
| |
| return defaultUri; |
| } |
| |
| public static boolean isHardwareKeyboardAvailable(Context context) { |
| return context.getResources().getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS; |
| } |
| |
| public static void ensureKeyboardPresent(Context context, AlertDialog dialog) { |
| if (!isHardwareKeyboardAvailable(context)) { |
| dialog.getWindow().setSoftInputMode( |
| WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); |
| } |
| } |
| |
| /** |
| * Returns true if "Documents" root should be shown. |
| */ |
| public static boolean shouldShowDocumentsRoot(Context context) { |
| return context.getResources().getBoolean(R.bool.show_documents_root); |
| } |
| |
| /* |
| * Returns true if the local/device storage root must be visible (this also hides |
| * the option to toggle visibility in the menu.) |
| */ |
| public static boolean mustShowDeviceRoot(Intent intent) { |
| return intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, false); |
| } |
| |
| public static String getDeviceName(ContentResolver resolver) { |
| // We match the value supplied by ExternalStorageProvider for |
| // the internal storage root. |
| return Settings.Global.getString(resolver, Settings.Global.DEVICE_NAME); |
| } |
| |
| public static void checkMainLoop() { |
| if (Looper.getMainLooper() != Looper.myLooper()) { |
| Log.e(TAG, "Calling from non-UI thread!"); |
| } |
| } |
| |
| /** |
| * This method exists solely to smooth over the fact that two different types of |
| * views cannot be bound to the same id in different layouts. "What's this crazy-pants |
| * stuff?", you say? Here's an example: |
| * |
| * The main DocumentsUI view (aka "Files app") when running on a phone has a drop-down |
| * "breadcrumb" (file path representation) in both landscape and portrait orientation. |
| * Larger format devices, like a tablet, use a horizontal "Dir1 > Dir2 > Dir3" format |
| * breadcrumb in landscape layouts, but the regular drop-down breadcrumb in portrait |
| * mode. |
| * |
| * Our initial inclination was to give each of those views the same ID (as they both |
| * implement the same "Breadcrumb" interface). But at runtime, when rotating a device |
| * from one orientation to the other, deeeeeeep within the UI toolkit a exception |
| * would happen, because one view instance (drop-down) was being inflated in place of |
| * another (horizontal). I'm writing this code comment significantly after the face, |
| * so I don't recall all of the details, but it had to do with View type-checking the |
| * Parcelable state in onRestore, or something like that. Either way, this isn't |
| * allowed (my patch to fix this was rejected). |
| * |
| * To work around this we have this cute little method that accepts multiple |
| * resource IDs, and along w/ type inference finds our view, no matter which |
| * id it is wearing, and returns it. |
| */ |
| @SuppressWarnings("TypeParameterUnusedInFormals") |
| public static @Nullable <T> T findView(Activity activity, int... resources) { |
| for (int id : resources) { |
| @SuppressWarnings("unchecked") |
| View view = activity.findViewById(id); |
| if (view != null) { |
| return (T) view; |
| } |
| } |
| return null; |
| } |
| } |