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