Home | History | Annotate | Download | only in base
      1 /*
      2  * Copyright (C) 2015 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 
     17 package com.android.documentsui.base;
     18 
     19 import android.annotation.PluralsRes;
     20 import android.app.Activity;
     21 import android.app.AlertDialog;
     22 import android.content.ContentResolver;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.pm.ApplicationInfo;
     26 import android.content.pm.PackageManager.NameNotFoundException;
     27 import android.content.res.Configuration;
     28 import android.net.Uri;
     29 import android.os.Build;
     30 import android.os.Looper;
     31 import android.provider.DocumentsContract;
     32 import android.provider.Settings;
     33 import android.text.TextUtils;
     34 import android.text.format.DateUtils;
     35 import android.text.format.Time;
     36 import android.util.Log;
     37 import android.view.WindowManager;
     38 
     39 import com.android.documentsui.R;
     40 import com.android.documentsui.ui.MessageBuilder;
     41 
     42 import java.io.PrintWriter;
     43 import java.io.StringWriter;
     44 import java.text.Collator;
     45 import java.util.ArrayList;
     46 import java.util.List;
     47 
     48 import javax.annotation.Nullable;
     49 
     50 /** @hide */
     51 public final class Shared {
     52 
     53     public static final String TAG = "Documents";
     54 
     55     public static final boolean DEBUG = Build.IS_DEBUGGABLE;
     56     public static final boolean VERBOSE = DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
     57 
     58     /** Intent action name to pick a copy destination. */
     59     public static final String ACTION_PICK_COPY_DESTINATION =
     60             "com.android.documentsui.PICK_COPY_DESTINATION";
     61 
     62     /**
     63      * Extra boolean flag for {@link #ACTION_PICK_COPY_DESTINATION}, which
     64      * specifies if the destination directory needs to create new directory or not.
     65      */
     66     public static final String EXTRA_DIRECTORY_COPY = "com.android.documentsui.DIRECTORY_COPY";
     67 
     68     /**
     69      * Extra flag used to store the current stack so user opens in right spot.
     70      */
     71     public static final String EXTRA_STACK = "com.android.documentsui.STACK";
     72 
     73     /**
     74      * Extra flag used to store query of type String in the bundle.
     75      */
     76     public static final String EXTRA_QUERY = "query";
     77 
     78     /**
     79      * Extra flag used to store state of type State in the bundle.
     80      */
     81     public static final String EXTRA_STATE = "state";
     82 
     83     /**
     84      * Extra flag used to store root of type RootInfo in the bundle.
     85      */
     86     public static final String EXTRA_ROOT = "root";
     87 
     88     /**
     89      * Extra flag used to store document of DocumentInfo type in the bundle.
     90      */
     91     public static final String EXTRA_DOC = "document";
     92 
     93     /**
     94      * Extra flag used to store DirectoryFragment's selection of Selection type in the bundle.
     95      */
     96     public static final String EXTRA_SELECTION = "selection";
     97 
     98     /**
     99      * Extra flag used to store DirectoryFragment's ignore state of boolean type in the bundle.
    100      */
    101     public static final String EXTRA_IGNORE_STATE = "ignoreState";
    102 
    103     /**
    104      * Extra for an Intent for enabling performance benchmark. Used only by tests.
    105      */
    106     public static final String EXTRA_BENCHMARK = "com.android.documentsui.benchmark";
    107 
    108     /**
    109      * Extra flag used to signify to inspector that debug section can be shown.
    110      */
    111     public static final String EXTRA_SHOW_DEBUG = "com.android.documentsui.SHOW_DEBUG";
    112 
    113     /**
    114      * Maximum number of items in a Binder transaction packet.
    115      */
    116     public static final int MAX_DOCS_IN_INTENT = 500;
    117 
    118     /**
    119      * Animation duration of checkbox in directory list/grid in millis.
    120      */
    121     public static final int CHECK_ANIMATION_DURATION = 100;
    122 
    123     private static final Collator sCollator;
    124 
    125     static {
    126         sCollator = Collator.getInstance();
    127         sCollator.setStrength(Collator.SECONDARY);
    128     }
    129 
    130     /**
    131      * @deprecated use {@link MessageBuilder#getQuantityString}
    132      */
    133     @Deprecated
    134     public static final String getQuantityString(Context context, @PluralsRes int resourceId, int quantity) {
    135         return context.getResources().getQuantityString(resourceId, quantity, quantity);
    136     }
    137 
    138     public static String formatTime(Context context, long when) {
    139         // TODO: DateUtils should make this easier
    140         Time then = new Time();
    141         then.set(when);
    142         Time now = new Time();
    143         now.setToNow();
    144 
    145         int flags = DateUtils.FORMAT_NO_NOON | DateUtils.FORMAT_NO_MIDNIGHT
    146                 | DateUtils.FORMAT_ABBREV_ALL;
    147 
    148         if (then.year != now.year) {
    149             flags |= DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE;
    150         } else if (then.yearDay != now.yearDay) {
    151             flags |= DateUtils.FORMAT_SHOW_DATE;
    152         } else {
    153             flags |= DateUtils.FORMAT_SHOW_TIME;
    154         }
    155 
    156         return DateUtils.formatDateTime(context, when, flags);
    157     }
    158 
    159     /**
    160      * A convenient way to transform any list into a (parcelable) ArrayList.
    161      * Uses cast if possible, else creates a new list with entries from {@code list}.
    162      */
    163     public static <T> ArrayList<T> asArrayList(List<T> list) {
    164         return list instanceof ArrayList
    165             ? (ArrayList<T>) list
    166             : new ArrayList<>(list);
    167     }
    168 
    169     /**
    170      * Compare two strings against each other using system default collator in a
    171      * case-insensitive mode. Clusters strings prefixed with {@link DIR_PREFIX}
    172      * before other items.
    173      */
    174     public static int compareToIgnoreCaseNullable(String lhs, String rhs) {
    175         final boolean leftEmpty = TextUtils.isEmpty(lhs);
    176         final boolean rightEmpty = TextUtils.isEmpty(rhs);
    177 
    178         if (leftEmpty && rightEmpty) return 0;
    179         if (leftEmpty) return -1;
    180         if (rightEmpty) return 1;
    181 
    182         return sCollator.compare(lhs, rhs);
    183     }
    184 
    185     /**
    186      * Returns the calling package, possibly overridden by EXTRA_PACKAGE_NAME.
    187      * @param activity
    188      * @return
    189      */
    190     public static String getCallingPackageName(Activity activity) {
    191         String callingPackage = activity.getCallingPackage();
    192         // System apps can set the calling package name using an extra.
    193         try {
    194             ApplicationInfo info =
    195                     activity.getPackageManager().getApplicationInfo(callingPackage, 0);
    196             if (info.isSystemApp() || info.isUpdatedSystemApp()) {
    197                 final String extra = activity.getIntent().getStringExtra(
    198                         DocumentsContract.EXTRA_PACKAGE_NAME);
    199                 if (extra != null && !TextUtils.isEmpty(extra)) {
    200                     callingPackage = extra;
    201                 }
    202             }
    203         } catch (NameNotFoundException e) {
    204             // Couldn't lookup calling package info. This isn't really
    205             // gonna happen, given that we're getting the name of the
    206             // calling package from trusty old Activity.getCallingPackage.
    207             // For that reason, we ignore this exception.
    208         }
    209         return callingPackage;
    210     }
    211 
    212     /**
    213      * Returns the default directory to be presented after starting the activity.
    214      * Method can be overridden if the change of the behavior of the the child activity is needed.
    215      */
    216     public static Uri getDefaultRootUri(Activity activity) {
    217         Uri defaultUri = Uri.parse(activity.getResources().getString(R.string.default_root_uri));
    218 
    219         if (!DocumentsContract.isRootUri(activity, defaultUri)) {
    220             throw new RuntimeException("Default Root URI is not a valid root URI.");
    221         }
    222 
    223         return defaultUri;
    224     }
    225 
    226     public static boolean isHardwareKeyboardAvailable(Context context) {
    227         return context.getResources().getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS;
    228     }
    229 
    230     public static void ensureKeyboardPresent(Context context, AlertDialog dialog) {
    231         if (!isHardwareKeyboardAvailable(context)) {
    232             dialog.getWindow().setSoftInputMode(
    233                     WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
    234         }
    235     }
    236 
    237     /**
    238      * Returns true if "Documents" root should be shown.
    239      */
    240     public static boolean shouldShowDocumentsRoot(Context context) {
    241         return context.getResources().getBoolean(R.bool.show_documents_root);
    242     }
    243 
    244     /*
    245      * Returns true if the local/device storage root must be visible (this also hides
    246      * the option to toggle visibility in the menu.)
    247      */
    248     public static boolean mustShowDeviceRoot(Intent intent) {
    249         return intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, false);
    250     }
    251 
    252     public static String getDeviceName(ContentResolver resolver) {
    253         // We match the value supplied by ExternalStorageProvider for
    254         // the internal storage root.
    255         return Settings.Global.getString(resolver, Settings.Global.DEVICE_NAME);
    256     }
    257 
    258     public static void checkMainLoop() {
    259         if (Looper.getMainLooper() != Looper.myLooper()) {
    260             Log.e(TAG, "Calling from non-UI thread!");
    261         }
    262     }
    263 
    264     public static @Nullable <T> T findView(Activity activity, int... resources) {
    265         for (int id : resources) {
    266             @SuppressWarnings("unchecked")
    267             T r = (T) activity.findViewById(id);
    268             if (r != null) {
    269                 return r;
    270             }
    271         }
    272         return null;
    273     }
    274 }
    275