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