1/*
2 * Copyright (C) 2017 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 */
16package com.android.documentsui.inspector;
17
18import static android.provider.DocumentsContract.Document.FLAG_SUPPORTS_SETTINGS;
19import static com.android.internal.util.Preconditions.checkArgument;
20
21import android.app.Activity;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.PackageManager;
25import android.graphics.drawable.Drawable;
26import android.net.Uri;
27import android.provider.DocumentsContract;
28import android.support.annotation.Nullable;
29import android.support.annotation.VisibleForTesting;
30import android.view.View;
31import android.view.View.OnClickListener;
32import com.android.documentsui.DocumentsApplication;
33import com.android.documentsui.ProviderExecutor;
34import com.android.documentsui.R;
35import com.android.documentsui.base.DocumentInfo;
36import com.android.documentsui.base.Lookup;
37import com.android.documentsui.inspector.actions.Action;
38import com.android.documentsui.inspector.actions.ClearDefaultAppAction;
39import com.android.documentsui.inspector.actions.ShowInProviderAction;
40import com.android.documentsui.roots.ProvidersAccess;
41import com.android.documentsui.ui.Snackbars;
42import java.util.concurrent.Executor;
43import java.util.function.Consumer;
44/**
45 * A controller that coordinates retrieving document information and sending it to the view.
46 */
47public final class InspectorController {
48
49    private final Loader mLoader;
50    private final Consumer<DocumentInfo> mHeader;
51    private final DetailsDisplay mDetails;
52    private final ActionDisplay mShowProvider;
53    private final ActionDisplay mAppDefaults;
54    private final Consumer<DocumentInfo> mDebugView;
55    private final boolean mShowDebug;
56    private final Context mContext;
57    private final PackageManager mPackageManager;
58    private final ProvidersAccess mProviders;
59    private final Runnable mShowSnackbar;
60    private final Lookup<String, Executor> mExecutors;
61
62    /**
63     * InspectorControllerTest relies on this controller.
64     */
65    @VisibleForTesting
66    public InspectorController(Context context, Loader loader, PackageManager pm,
67            ProvidersAccess providers, boolean showDebug, Consumer<DocumentInfo> header,
68            DetailsDisplay details, ActionDisplay showProvider, ActionDisplay appDefaults,
69            Consumer<DocumentInfo> debugView, Lookup<String, Executor> executors,
70            Runnable showSnackbar) {
71
72        checkArgument(context != null);
73        checkArgument(loader != null);
74        checkArgument(pm != null);
75        checkArgument(providers != null);
76        checkArgument(header != null);
77        checkArgument(details != null);
78        checkArgument(showProvider != null);
79        checkArgument(appDefaults != null);
80        checkArgument(debugView != null);
81        checkArgument(showSnackbar != null);
82        checkArgument(executors != null);
83
84        mContext = context;
85        mLoader = loader;
86        mPackageManager = pm;
87        mShowDebug = showDebug;
88        mProviders = providers;
89        mHeader = header;
90        mDetails = details;
91        mShowProvider = showProvider;
92        mAppDefaults = appDefaults;
93        mDebugView = debugView;
94        mExecutors = executors;
95        mShowSnackbar = showSnackbar;
96    }
97
98    public InspectorController(Activity activity, Loader loader, View layout, boolean showDebug) {
99
100        this(activity,
101                loader,
102                activity.getPackageManager(),
103                DocumentsApplication.getProvidersCache (activity),
104                showDebug,
105                (HeaderView) layout.findViewById(R.id.inspector_header_view),
106                (DetailsView) layout.findViewById(R.id.inspector_details_view),
107                (ActionDisplay) layout.findViewById(R.id.inspector_show_in_provider_view),
108                (ActionDisplay) layout.findViewById(R.id.inspector_app_defaults_view),
109                (DebugView) layout.findViewById(R.id.inspector_debug_view),
110                ProviderExecutor::forAuthority,
111                () -> {
112                    // using a runnable to support unit testing this feature.
113                    Snackbars.showInspectorError(activity);
114                }
115        );
116        if (showDebug) {
117            layout.findViewById(R.id.inspector_debug_view).setVisibility(View.VISIBLE);
118        }
119    }
120
121    public void reset() {
122        mLoader.reset();
123    }
124
125    public void loadInfo(Uri uri) {
126        mLoader.loadDocInfo(uri, this::updateView);
127    }
128
129    /**
130     * Updates the view with documentInfo.
131     */
132    @Nullable
133    public void updateView(@Nullable DocumentInfo docInfo) {
134
135        if (docInfo == null) {
136            mShowSnackbar.run();
137        } else {
138            mHeader.accept(docInfo);
139            mDetails.accept(docInfo);
140
141            if (docInfo.isDirectory()) {
142                mLoader.loadDirCount(docInfo, this::displayChildCount);
143            } else {
144
145                mShowProvider.setVisible(docInfo.isSettingsSupported());
146                if (docInfo.isSettingsSupported()) {
147                    Action showProviderAction =
148                        new ShowInProviderAction(mContext, mPackageManager, docInfo, mProviders);
149                    mShowProvider.init(
150                            showProviderAction,
151                            (view) -> {
152                                showInProvider(docInfo.derivedUri);
153                            });
154                }
155
156                Action defaultAction =
157                        new ClearDefaultAppAction(mContext, mPackageManager, docInfo);
158
159                mAppDefaults.setVisible(defaultAction.canPerformAction());
160                if (defaultAction.canPerformAction()) {
161                    mAppDefaults.init(
162                            defaultAction,
163                            (View) -> {
164                                clearDefaultApp(defaultAction.getPackageName());
165                            });
166                }
167            }
168
169            if (mShowDebug) {
170                mDebugView.accept(docInfo);
171            }
172        }
173    }
174
175    /**
176     * Displays a directory's information to the view.
177     *
178     * @param count - number of items in the directory.
179     */
180    private void displayChildCount(Integer count) {
181        mDetails.setChildrenCount(count);
182    }
183
184    /**
185     * Shows the selected document in it's content provider.
186     *
187     * @param DocumentInfo whose flag FLAG_SUPPORTS_SETTINGS is set.
188     */
189    public void showInProvider(Uri uri) {
190
191        Intent intent = new Intent(DocumentsContract.ACTION_DOCUMENT_SETTINGS);
192        intent.setPackage(mProviders.getPackageName(uri.getAuthority()));
193        intent.addCategory(Intent.CATEGORY_DEFAULT);
194        intent.setData(uri);
195        mContext.startActivity(intent);
196    }
197
198    /**
199     * Clears the default app that's opens that file type.
200     *
201     * @param packageName of the preferred app.
202     */
203    public void clearDefaultApp(String packageName) {
204        assert packageName != null;
205        mPackageManager.clearPackagePreferredActivities(packageName);
206
207        mAppDefaults.setAppIcon(null);
208        mAppDefaults.setAppName(mContext.getString(R.string.handler_app_not_selected));
209        mAppDefaults.showAction(false);
210    }
211
212    /**
213     * Interface for loading document metadata.
214     */
215    public interface Loader {
216
217        /**
218         * Starts the Asynchronous process of loading file data.
219         *
220         * @param uri - A content uri to query metadata from.
221         * @param callback - Function to be called when the loader has finished loading metadata. A
222         * DocumentInfo will be sent to this method. DocumentInfo may be null.
223         */
224        void loadDocInfo(Uri uri, Consumer<DocumentInfo> callback);
225
226        /**
227         * Loads a folders item count.
228         * @param directory - a documentInfo thats a directory.
229         * @param callback - Function to be called when the loader has finished loading the number
230         * of children.
231         */
232        void loadDirCount(DocumentInfo directory, Consumer<Integer> callback);
233
234        /**
235         * Deletes all loader id's when android lifecycle ends.
236         */
237        void reset();
238    }
239
240    /**
241     * This interface is for unit testing.
242     */
243    public interface ActionDisplay {
244
245        /**
246         * Initializes the view based on the action.
247         * @param action - ClearDefaultAppAction or ShowInProviderAction
248         * @param listener - listener for when the action is pressed.
249         */
250        void init(Action action, OnClickListener listener);
251
252        /**
253         * Makes the action visible.
254         */
255        void setVisible(boolean visible);
256
257        void setActionHeader(String header);
258
259        void setAppIcon(Drawable icon);
260
261        void setAppName(String name);
262
263        void showAction(boolean visible);
264    }
265
266    /**
267     * Provides details about a file.
268     */
269    public interface DetailsDisplay {
270
271        void accept(DocumentInfo info);
272
273        void setChildrenCount(int count);
274    }
275}