Home | History | Annotate | Download | only in inspector
      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 
     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
     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
     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
    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 
    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 
    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
    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
    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
    174         public void onLoaderReset(android.content.Loader<Cursor> loader) {
    175             if (mObserver != null) {
    176                 mContext.getContentResolver().unregisterContentObserver(mObserver);
    177             }
    178         }
    179 
    180         public ContentObserver getObserver() {
    181             return mObserver;
    182         }
    183     }
    184 
    185     private static final class InspectorContentObserver extends ContentObserver {
    186         private final Runnable mContentChangedCallback;
    187 
    188         public InspectorContentObserver(Runnable contentChangedCallback) {
    189             super(new Handler(Looper.getMainLooper()));
    190             mContentChangedCallback = contentChangedCallback;
    191         }
    192 
    193         @Override
    194         public void onChange(boolean selfChange) {
    195             mContentChangedCallback.run();
    196         }
    197     }
    198 }