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