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 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 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 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 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 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 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 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 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 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 192 public void onLoaderReset(android.content.Loader<Cursor> loader) { 193 if (mObserver != null) { 194 mContext.getContentResolver().unregisterContentObserver(mObserver); 195 } 196 } 197 198 public ContentObserver getObserver() { 199 return mObserver; 200 } 201 } 202 203 private static final class InspectorContentObserver extends ContentObserver { 204 private final Runnable mContentChangedCallback; 205 206 public InspectorContentObserver(Runnable contentChangedCallback) { 207 super(new Handler(Looper.getMainLooper())); 208 mContentChangedCallback = contentChangedCallback; 209 } 210 211 @Override 212 public void onChange(boolean selfChange) { 213 mContentChangedCallback.run(); 214 } 215 } 216 } 217