• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static com.android.documentsui.base.SharedMinimal.TAG;
20 
21 import android.annotation.PluralsRes;
22 import android.app.Activity;
23 import android.app.AlertDialog;
24 import android.content.ContentResolver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.ApplicationInfo;
28 import android.content.pm.PackageManager.NameNotFoundException;
29 import android.content.res.Configuration;
30 import android.net.Uri;
31 import android.os.Build;
32 import android.os.Looper;
33 import android.provider.DocumentsContract;
34 import android.provider.Settings;
35 import android.text.TextUtils;
36 import android.text.format.DateUtils;
37 import android.text.format.Time;
38 import android.util.Log;
39 import android.view.View;
40 import android.view.WindowManager;
41 
42 import com.android.documentsui.R;
43 import com.android.documentsui.ui.MessageBuilder;
44 
45 import java.text.Collator;
46 import java.util.ArrayList;
47 import java.util.List;
48 
49 import javax.annotation.Nullable;
50 
51 /** @hide */
52 public final class Shared {
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     // These values track values declared in MediaDocumentsProvider.
59     public static final String METADATA_KEY_AUDIO = "android.media.metadata.audio";
60     public static final String METADATA_KEY_VIDEO = "android.media.metadata.video";
61     public static final String METADATA_VIDEO_LATITUDE = "android.media.metadata.video:latitude";
62     public static final String METADATA_VIDEO_LONGITUTE = "android.media.metadata.video:longitude";
63 
64     /**
65      * Extra boolean flag for {@link #ACTION_PICK_COPY_DESTINATION}, which
66      * specifies if the destination directory needs to create new directory or not.
67      */
68     public static final String EXTRA_DIRECTORY_COPY = "com.android.documentsui.DIRECTORY_COPY";
69 
70     /**
71      * Extra flag used to store the current stack so user opens in right spot.
72      */
73     public static final String EXTRA_STACK = "com.android.documentsui.STACK";
74 
75     /**
76      * Extra flag used to store query of type String in the bundle.
77      */
78     public static final String EXTRA_QUERY = "query";
79 
80     /**
81      * Extra flag used to store state of type State in the bundle.
82      */
83     public static final String EXTRA_STATE = "state";
84 
85     /**
86      * Extra flag used to store root of type RootInfo in the bundle.
87      */
88     public static final String EXTRA_ROOT = "root";
89 
90     /**
91      * Extra flag used to store document of DocumentInfo type in the bundle.
92      */
93     public static final String EXTRA_DOC = "document";
94 
95     /**
96      * Extra flag used to store DirectoryFragment's selection of Selection type in the bundle.
97      */
98     public static final String EXTRA_SELECTION = "selection";
99 
100     /**
101      * Extra flag used to store DirectoryFragment's ignore state of boolean type in the bundle.
102      */
103     public static final String EXTRA_IGNORE_STATE = "ignoreState";
104 
105     /**
106      * Extra for an Intent for enabling performance benchmark. Used only by tests.
107      */
108     public static final String EXTRA_BENCHMARK = "com.android.documentsui.benchmark";
109 
110     /**
111      * Extra flag used to signify to inspector that debug section can be shown.
112      */
113     public static final String EXTRA_SHOW_DEBUG = "com.android.documentsui.SHOW_DEBUG";
114 
115     /**
116      * Maximum number of items in a Binder transaction packet.
117      */
118     public static final int MAX_DOCS_IN_INTENT = 500;
119 
120     /**
121      * Animation duration of checkbox in directory list/grid in millis.
122      */
123     public static final int CHECK_ANIMATION_DURATION = 100;
124 
125     private static final Collator sCollator;
126 
127     static {
128         sCollator = Collator.getInstance();
129         sCollator.setStrength(Collator.SECONDARY);
130     }
131 
132     /**
133      * @deprecated use {@link MessageBuilder#getQuantityString}
134      */
135     @Deprecated
getQuantityString(Context context, @PluralsRes int resourceId, int quantity)136     public static final String getQuantityString(Context context, @PluralsRes int resourceId, int quantity) {
137         return context.getResources().getQuantityString(resourceId, quantity, quantity);
138     }
139 
formatTime(Context context, long when)140     public static String formatTime(Context context, long when) {
141         // TODO: DateUtils should make this easier
142         Time then = new Time();
143         then.set(when);
144         Time now = new Time();
145         now.setToNow();
146 
147         int flags = DateUtils.FORMAT_NO_NOON | DateUtils.FORMAT_NO_MIDNIGHT
148                 | DateUtils.FORMAT_ABBREV_ALL;
149 
150         if (then.year != now.year) {
151             flags |= DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE;
152         } else if (then.yearDay != now.yearDay) {
153             flags |= DateUtils.FORMAT_SHOW_DATE;
154         } else {
155             flags |= DateUtils.FORMAT_SHOW_TIME;
156         }
157 
158         return DateUtils.formatDateTime(context, when, flags);
159     }
160 
161     /**
162      * A convenient way to transform any list into a (parcelable) ArrayList.
163      * Uses cast if possible, else creates a new list with entries from {@code list}.
164      */
asArrayList(List<T> list)165     public static <T> ArrayList<T> asArrayList(List<T> list) {
166         return list instanceof ArrayList
167             ? (ArrayList<T>) list
168             : new ArrayList<>(list);
169     }
170 
171     /**
172      * Compare two strings against each other using system default collator in a
173      * case-insensitive mode. Clusters strings prefixed with {@link DIR_PREFIX}
174      * before other items.
175      */
compareToIgnoreCaseNullable(String lhs, String rhs)176     public static int compareToIgnoreCaseNullable(String lhs, String rhs) {
177         final boolean leftEmpty = TextUtils.isEmpty(lhs);
178         final boolean rightEmpty = TextUtils.isEmpty(rhs);
179 
180         if (leftEmpty && rightEmpty) return 0;
181         if (leftEmpty) return -1;
182         if (rightEmpty) return 1;
183 
184         return sCollator.compare(lhs, rhs);
185     }
186 
187     /**
188      * Returns the calling package, possibly overridden by EXTRA_PACKAGE_NAME.
189      * @param activity
190      * @return
191      */
getCallingPackageName(Activity activity)192     public static String getCallingPackageName(Activity activity) {
193         String callingPackage = activity.getCallingPackage();
194         // System apps can set the calling package name using an extra.
195         try {
196             ApplicationInfo info =
197                     activity.getPackageManager().getApplicationInfo(callingPackage, 0);
198             if (info.isSystemApp() || info.isUpdatedSystemApp()) {
199                 final String extra = activity.getIntent().getStringExtra(
200                         DocumentsContract.EXTRA_PACKAGE_NAME);
201                 if (extra != null && !TextUtils.isEmpty(extra)) {
202                     callingPackage = extra;
203                 }
204             }
205         } catch (NameNotFoundException e) {
206             // Couldn't lookup calling package info. This isn't really
207             // gonna happen, given that we're getting the name of the
208             // calling package from trusty old Activity.getCallingPackage.
209             // For that reason, we ignore this exception.
210         }
211         return callingPackage;
212     }
213 
214     /**
215      * Returns the default directory to be presented after starting the activity.
216      * Method can be overridden if the change of the behavior of the the child activity is needed.
217      */
getDefaultRootUri(Activity activity)218     public static Uri getDefaultRootUri(Activity activity) {
219         Uri defaultUri = Uri.parse(activity.getResources().getString(R.string.default_root_uri));
220 
221         if (!DocumentsContract.isRootUri(activity, defaultUri)) {
222             throw new RuntimeException("Default Root URI is not a valid root URI.");
223         }
224 
225         return defaultUri;
226     }
227 
isHardwareKeyboardAvailable(Context context)228     public static boolean isHardwareKeyboardAvailable(Context context) {
229         return context.getResources().getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS;
230     }
231 
ensureKeyboardPresent(Context context, AlertDialog dialog)232     public static void ensureKeyboardPresent(Context context, AlertDialog dialog) {
233         if (!isHardwareKeyboardAvailable(context)) {
234             dialog.getWindow().setSoftInputMode(
235                     WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
236         }
237     }
238 
239     /**
240      * Returns true if "Documents" root should be shown.
241      */
shouldShowDocumentsRoot(Context context)242     public static boolean shouldShowDocumentsRoot(Context context) {
243         return context.getResources().getBoolean(R.bool.show_documents_root);
244     }
245 
246     /*
247      * Returns true if the local/device storage root must be visible (this also hides
248      * the option to toggle visibility in the menu.)
249      */
mustShowDeviceRoot(Intent intent)250     public static boolean mustShowDeviceRoot(Intent intent) {
251         return intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, false);
252     }
253 
getDeviceName(ContentResolver resolver)254     public static String getDeviceName(ContentResolver resolver) {
255         // We match the value supplied by ExternalStorageProvider for
256         // the internal storage root.
257         return Settings.Global.getString(resolver, Settings.Global.DEVICE_NAME);
258     }
259 
checkMainLoop()260     public static void checkMainLoop() {
261         if (Looper.getMainLooper() != Looper.myLooper()) {
262             Log.e(TAG, "Calling from non-UI thread!");
263         }
264     }
265 
266     /**
267      * This method exists solely to smooth over the fact that two different types of
268      * views cannot be bound to the same id in different layouts. "What's this crazy-pants
269      * stuff?", you say? Here's an example:
270      *
271      * The main DocumentsUI view (aka "Files app") when running on a phone has a drop-down
272      * "breadcrumb" (file path representation) in both landscape and portrait orientation.
273      * Larger format devices, like a tablet, use a horizontal "Dir1 > Dir2 > Dir3" format
274      * breadcrumb in landscape layouts, but the regular drop-down breadcrumb in portrait
275      * mode.
276      *
277      * Our initial inclination was to give each of those views the same ID (as they both
278      * implement the same "Breadcrumb" interface). But at runtime, when rotating a device
279      * from one orientation to the other, deeeeeeep within the UI toolkit a exception
280      * would happen, because one view instance (drop-down) was being inflated in place of
281      * another (horizontal). I'm writing this code comment significantly after the face,
282      * so I don't recall all of the details, but it had to do with View type-checking the
283      * Parcelable state in onRestore, or something like that. Either way, this isn't
284      * allowed (my patch to fix this was rejected).
285      *
286      * To work around this we have this cute little method that accepts multiple
287      * resource IDs, and along w/ type inference finds our view, no matter which
288      * id it is wearing, and returns it.
289      */
290     @SuppressWarnings("TypeParameterUnusedInFormals")
findView(Activity activity, int... resources)291     public static @Nullable <T> T findView(Activity activity, int... resources) {
292         for (int id : resources) {
293             @SuppressWarnings("unchecked")
294             View view = activity.findViewById(id);
295             if (view != null) {
296                 return (T) view;
297             }
298         }
299         return null;
300     }
301 
Shared()302     private Shared() {
303         throw new UnsupportedOperationException("provides static fields only");
304     }
305 }
306