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
17package com.android.documentsui.base;
18
19import android.annotation.PluralsRes;
20import android.app.Activity;
21import android.app.AlertDialog;
22import android.content.ContentResolver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.pm.ApplicationInfo;
26import android.content.pm.PackageManager.NameNotFoundException;
27import android.content.res.Configuration;
28import android.net.Uri;
29import android.os.Build;
30import android.os.Looper;
31import android.provider.DocumentsContract;
32import android.provider.Settings;
33import android.text.TextUtils;
34import android.text.format.DateUtils;
35import android.text.format.Time;
36import android.util.Log;
37import android.view.WindowManager;
38
39import com.android.documentsui.R;
40import com.android.documentsui.ui.MessageBuilder;
41
42import java.io.PrintWriter;
43import java.io.StringWriter;
44import java.text.Collator;
45import java.util.ArrayList;
46import java.util.List;
47
48import javax.annotation.Nullable;
49
50/** @hide */
51public 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