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