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