• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
16 package com.android.documentsui.inspector;
17 
18 import static com.android.internal.util.Preconditions.checkArgument;
19 
20 import android.app.LoaderManager;
21 import android.app.LoaderManager.LoaderCallbacks;
22 import android.content.Context;
23 import android.content.CursorLoader;
24 import android.database.ContentObserver;
25 import android.database.Cursor;
26 import android.net.Uri;
27 import android.os.Bundle;
28 import android.os.Handler;
29 import android.os.Looper;
30 import android.provider.DocumentsContract;
31 import android.support.annotation.Nullable;
32 
33 import com.android.documentsui.base.DocumentInfo;
34 import com.android.documentsui.inspector.InspectorController.DataSupplier;
35 
36 import java.util.ArrayList;
37 import java.util.List;
38 import java.util.function.Consumer;
39 
40 /**
41  * Asynchronously loads a document data for the inspector.
42  *
43  * <p>This loader is not a Loader! Its our own funky loader.
44  */
45 public class RuntimeDataSupplier implements DataSupplier {
46 
47     private final Context mContext;
48     private final LoaderManager mLoaderMgr;
49     private final List<Integer> loaderIds = new ArrayList<>();
50     private @Nullable Callbacks mDocCallbacks;
51     private @Nullable Callbacks mDirCallbacks;
52     private @Nullable LoaderCallbacks<Bundle> mMetadataCallbacks;
53 
RuntimeDataSupplier(Context context, LoaderManager loaderMgr)54     public RuntimeDataSupplier(Context context, LoaderManager loaderMgr) {
55         checkArgument(context != null);
56         checkArgument(loaderMgr != null);
57         mContext = context;
58         mLoaderMgr = loaderMgr;
59     }
60 
61     /**
62      * Loads documents metadata.
63      */
64     @Override
loadDocInfo(Uri uri, Consumer<DocumentInfo> updateView)65     public void loadDocInfo(Uri uri, Consumer<DocumentInfo> updateView) {
66         //Check that we have correct Uri type and that the loader is not already created.
67         checkArgument(uri.getScheme().equals("content"));
68 
69         Consumer<Cursor> callback = new Consumer<Cursor>() {
70             @Override
71             public void accept(Cursor cursor) {
72 
73                 if (cursor == null || !cursor.moveToFirst()) {
74                     updateView.accept(null);
75                 } else {
76                     DocumentInfo docInfo = DocumentInfo.fromCursor(cursor, uri.getAuthority());
77                     updateView.accept(docInfo);
78                 }
79             }
80         };
81 
82         mDocCallbacks = new Callbacks(mContext, uri, callback);
83         mLoaderMgr.restartLoader(getNextLoaderId(), null, mDocCallbacks);
84     }
85 
86     /**
87      * Loads a directories item count.
88      */
89     @Override
loadDirCount(DocumentInfo directory, Consumer<Integer> updateView)90     public void loadDirCount(DocumentInfo directory, Consumer<Integer> updateView) {
91         checkArgument(directory.isDirectory());
92         Uri children = DocumentsContract.buildChildDocumentsUri(
93                 directory.authority, directory.documentId);
94 
95         Consumer<Cursor> callback = new Consumer<Cursor>() {
96             @Override
97             public void accept(Cursor cursor) {
98                 if(cursor != null && cursor.moveToFirst()) {
99                     updateView.accept(cursor.getCount());
100                 }
101             }
102         };
103 
104         mDirCallbacks = new Callbacks(mContext, children, callback);
105         mLoaderMgr.restartLoader(getNextLoaderId(), null, mDirCallbacks);
106     }
107 
108     @Override
getDocumentMetadata(Uri uri, Consumer<Bundle> callback)109     public void getDocumentMetadata(Uri uri, Consumer<Bundle> callback) {
110         mMetadataCallbacks = new LoaderCallbacks<Bundle>() {
111             @Override
112             public android.content.Loader<Bundle> onCreateLoader(int id, Bundle unused) {
113                 return new MetadataLoader(mContext, uri);
114             }
115 
116             @Override
117             public void onLoadFinished(android.content.Loader<Bundle> loader, Bundle data) {
118                 callback.accept(data);
119             }
120 
121             @Override
122             public void onLoaderReset(android.content.Loader<Bundle> loader) {
123             }
124         };
125 
126         // TODO: Listen for changes on content URI.
127         mLoaderMgr.restartLoader(getNextLoaderId(), null, mMetadataCallbacks);
128     }
129 
130     @Override
reset()131     public void reset() {
132         for (Integer id : loaderIds) {
133             mLoaderMgr.destroyLoader(id);
134         }
135         loaderIds.clear();
136 
137         if (mDocCallbacks != null && mDocCallbacks.getObserver() != null) {
138             mContext.getContentResolver().unregisterContentObserver(mDocCallbacks.getObserver());
139         }
140 
141         if (mDirCallbacks != null && mDirCallbacks.getObserver() != null) {
142             mContext.getContentResolver().unregisterContentObserver(mDirCallbacks.getObserver());
143         }
144     }
145 
getNextLoaderId()146     private int getNextLoaderId() {
147         int id = 0;
148         while(mLoaderMgr.getLoader(id) != null) {
149             id++;
150             checkArgument(id <= Integer.MAX_VALUE);
151         }
152         loaderIds.add(id);
153         return id;
154     }
155 
156     /**
157      * Implements the callback interface for cursor loader.
158      */
159     static final class Callbacks implements LoaderCallbacks<Cursor> {
160 
161         private final Context mContext;
162         private final Uri mUri;
163         private final Consumer<Cursor> mCallback;
164         private ContentObserver mObserver;
165 
Callbacks(Context context, Uri uri, Consumer<Cursor> callback)166         Callbacks(Context context, Uri uri, Consumer<Cursor> callback) {
167             checkArgument(context != null);
168             checkArgument(uri != null);
169             checkArgument(callback != null);
170             mContext = context;
171             mUri = uri;
172             mCallback = callback;
173         }
174 
175         @Override
onCreateLoader(int id, Bundle args)176         public android.content.Loader<Cursor> onCreateLoader(int id, Bundle args) {
177             return new CursorLoader(mContext, mUri, null, null, null, null);
178         }
179 
180         @Override
onLoadFinished(android.content.Loader<Cursor> loader, Cursor cursor)181         public void onLoadFinished(android.content.Loader<Cursor> loader, Cursor cursor) {
182 
183             if (cursor != null) {
184                 mObserver = new InspectorContentObserver(loader::onContentChanged);
185                 cursor.registerContentObserver(mObserver);
186             }
187 
188             mCallback.accept(cursor);
189         }
190 
191         @Override
onLoaderReset(android.content.Loader<Cursor> loader)192         public void onLoaderReset(android.content.Loader<Cursor> loader) {
193             if (mObserver != null) {
194                 mContext.getContentResolver().unregisterContentObserver(mObserver);
195             }
196         }
197 
getObserver()198         public ContentObserver getObserver() {
199             return mObserver;
200         }
201     }
202 
203     private static final class InspectorContentObserver extends ContentObserver {
204         private final Runnable mContentChangedCallback;
205 
InspectorContentObserver(Runnable contentChangedCallback)206         public InspectorContentObserver(Runnable contentChangedCallback) {
207             super(new Handler(Looper.getMainLooper()));
208             mContentChangedCallback = contentChangedCallback;
209         }
210 
211         @Override
onChange(boolean selfChange)212         public void onChange(boolean selfChange) {
213             mContentChangedCallback.run();
214         }
215     }
216 }
217