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