Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0404bdca authored by Steve McKay's avatar Steve McKay
Browse files

Load EXIF data into new view.

Note: This CL loads EXIF data syncronously. Tracking a fix in: https://b.corp.google.com/u/0/issues/63925015

Bug: 62621970
Test: Run existing tests.
Change-Id: I892cad2a3c3d3f92724740b8e957524ab8d2e069
parent 0450d6c9
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -393,6 +393,9 @@
    <!-- Title of inspector's metadata info section. Note that this is probably camera EXIF data. -->
    <string name="inspector_metadata_section">Metadata</string>

    <!-- Title of inspector section displaying image specific information (like size and embedded camera details, like lat/long). -->
    <string name="inspector_exif_section">Image information</string>

    <!--The height and width of a photo. Note that this is probably camera EXIF data.-->
    <string name="metadata_dimensions">Dimensions</string>
    <!--The location of where a photo was taken. (i.e. latitude, longitude) Note that this is probably camera EXIF data.-->
+5 −0
Original line number Diff line number Diff line
@@ -223,6 +223,7 @@ public class DocumentInfo implements Durable, Parcelable {
                + ", isDeleteSupported=" + isDeleteSupported()
                + ", isCreateSupported=" + isCreateSupported()
                + ", isRenameSupported=" + isRenameSupported()
                + ", isMetadataSupported=" + isMetadataSupported()
                + "} @ "
                + derivedUri;
    }
@@ -255,6 +256,10 @@ public class DocumentInfo implements Durable, Parcelable {
        return (flags & Document.FLAG_SUPPORTS_RENAME) != 0;
    }

    public boolean isMetadataSupported() {
        return (flags & Document.FLAG_SUPPORTS_METADATA) != 0;
    }

    public boolean isThumbnailSupported() {
        return (flags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
    }
+1 −0
Original line number Diff line number Diff line
@@ -53,6 +53,7 @@ public class DebugView extends TableView implements Consumer<DocumentInfo> {
        put("Is virtual", info.isVirtual());
        put("Supports create", info.isCreateSupported());
        put("Supports delete", info.isDeleteSupported());
        put("Supports metadata", info.isMetadataSupported());
        put("Supports rename", info.isRenameSupported());
        put("Supports settings", info.isSettingsSupported());
        put("Supports thumbnail", info.isThumbnailSupported());
+75 −23
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ import static com.android.internal.util.Preconditions.checkArgument;

import android.app.LoaderManager;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.AsyncTaskLoader;
import android.content.Context;
import android.content.CursorLoader;
import android.database.ContentObserver;
@@ -27,32 +28,37 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;

import android.provider.DocumentsContract;
import android.support.annotation.Nullable;
import android.util.Log;

import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.inspector.InspectorController.Loader;

import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

/**
 * Asynchronously loads a document's metadata for the inspector.
 * Asynchronously loads a document data for the inspector.
 */
public class DocumentLoader implements Loader {

    private static final String TAG = "DocumentLoader";

    private final Context mContext;
    private final LoaderManager mLoader;
    private List<Integer> loaderIds;
    private final LoaderManager mLoaderMgr;
    private final List<Integer> loaderIds = new ArrayList<>();
    private @Nullable Callbacks mDocCallbacks;
    private @Nullable Callbacks mDirCallbacks;
    private @Nullable LoaderCallbacks<Bundle> mMetadataCallbacks;

    public DocumentLoader(Context context, LoaderManager loader) {
    public DocumentLoader(Context context, LoaderManager loaderMgr) {
        checkArgument(context != null);
        checkArgument(loader != null);
        checkArgument(loaderMgr != null);
        mContext = context;
        mLoader = loader;
        loaderIds = new ArrayList<>();
        mLoaderMgr = loaderMgr;
    }

    /**
@@ -63,11 +69,6 @@ public class DocumentLoader implements Loader {
        //Check that we have correct Uri type and that the loader is not already created.
        checkArgument(uri.getScheme().equals("content"));

        //get a new loader id.
        int loadId = getNextLoaderId();
        checkArgument(mLoader.getLoader(loadId) == null);
        loaderIds.add(loadId);

        Consumer<Cursor> callback = new Consumer<Cursor>() {
            @Override
            public void accept(Cursor cursor) {
@@ -81,7 +82,7 @@ public class DocumentLoader implements Loader {
        };

        mDocCallbacks = new Callbacks(mContext, uri, callback);
        mLoader.restartLoader(loadId, null, mDocCallbacks);
        mLoaderMgr.restartLoader(getNextLoaderId(), null, mDocCallbacks);
    }

    /**
@@ -93,11 +94,6 @@ public class DocumentLoader implements Loader {
        Uri children = DocumentsContract.buildChildDocumentsUri(
                directory.authority, directory.documentId);

        //get a new loader id.
        int loadId = getNextLoaderId();
        checkArgument(mLoader.getLoader(loadId) == null);
        loaderIds.add(loadId);

        Consumer<Cursor> callback = new Consumer<Cursor>() {
            @Override
            public void accept(Cursor cursor) {
@@ -108,19 +104,74 @@ public class DocumentLoader implements Loader {
        };

        mDirCallbacks = new Callbacks(mContext, children, callback);
        mLoader.restartLoader(loadId, null, mDirCallbacks);
        mLoaderMgr.restartLoader(getNextLoaderId(), null, mDirCallbacks);
    }

    @Override
    public void getDocumentMetadata(Uri uri, Consumer<Bundle> callback) {

        // TODO: For some reason the async loading of metadata isn't working.
        // This is a hackaround. Tracking bug @ b/63925015
        try {
            Bundle syncData = DocumentsContract.getDocumentMetadata(
                    mContext.getContentResolver(),
                    uri,
                    null);
            callback.accept(syncData);
        } catch (FileNotFoundException e) {
            callback.accept(Bundle.EMPTY);
        }

        Log.d(TAG, "Loading document metadata.");

        mMetadataCallbacks = new LoaderCallbacks<Bundle>() {
            @Override
            public android.content.Loader<Bundle> onCreateLoader(int id, Bundle unused) {
                Log.d(TAG, "Creating loader for metadata.");
                return new AsyncTaskLoader<Bundle>(mContext) {
                    @Override
                    public Bundle loadInBackground() {
                        try {
                            Log.d(TAG, "Executing call to load metadata.");
                            return DocumentsContract.getDocumentMetadata(
                                    mContext.getContentResolver(),
                                    uri, null);
                        } catch (FileNotFoundException e) {
                            Log.e(TAG, "Failed to load metadata for doc: " + uri, e);
                        }

                        return null;
                    }
                };
            }

            @Override
            public void onLoadFinished(android.content.Loader<Bundle> loader, Bundle data) {
                Log.d(TAG, "Received document metadata. Relaying to callback.");
                callback.accept(data);
            }

            @Override
            public void onLoaderReset(android.content.Loader<Bundle> loader) {
                Log.d(TAG, "Document metadata reset. Yerp!");
            }
        };

        // TODO: Listen for changes on content URI.
        mLoaderMgr.restartLoader(getNextLoaderId(), null, mMetadataCallbacks);
    }

    @Override
    public void reset() {
        for (Integer id : loaderIds) {
            mLoader.destroyLoader(id);
            mLoaderMgr.destroyLoader(id);
        }
        loaderIds.clear();

        if (mDocCallbacks != null && mDocCallbacks.getObserver() != null) {
            mContext.getContentResolver().unregisterContentObserver(mDocCallbacks.getObserver());
        }

        if (mDirCallbacks != null && mDirCallbacks.getObserver() != null) {
            mContext.getContentResolver().unregisterContentObserver(mDocCallbacks.getObserver());
        }
@@ -128,10 +179,11 @@ public class DocumentLoader implements Loader {

    private int getNextLoaderId() {
        int id = 0;
        while(mLoader.getLoader(id) != null) {
        while(mLoaderMgr.getLoader(id) != null) {
            id++;
            checkArgument(id <= Integer.MAX_VALUE);
        }
        loaderIds.add(id);
        return id;
    }

+61 −30
Original line number Diff line number Diff line
@@ -15,7 +15,6 @@
 */
package com.android.documentsui.inspector;

import static android.provider.DocumentsContract.Document.FLAG_SUPPORTS_SETTINGS;
import static com.android.internal.util.Preconditions.checkArgument;

import android.annotation.StringRes;
@@ -32,6 +31,7 @@ import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.view.View;
import android.view.View.OnClickListener;

import com.android.documentsui.DocumentsApplication;
import com.android.documentsui.ProviderExecutor;
import com.android.documentsui.R;
@@ -42,6 +42,7 @@ import com.android.documentsui.inspector.actions.ClearDefaultAppAction;
import com.android.documentsui.inspector.actions.ShowInProviderAction;
import com.android.documentsui.roots.ProvidersAccess;
import com.android.documentsui.ui.Snackbars;

import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -172,34 +173,53 @@ public final class InspectorController {
                        });
                }
            }

            if (docInfo.isMetadataSupported()) {
                mLoader.getDocumentMetadata(
                        docInfo.derivedUri,
                        (Bundle bundle) -> {
                            onDocumentMetadataLoaded(docInfo.displayName, bundle);
                        });
            } else {
                mMetadata.setVisible(false);
            }

            if (mShowDebug) {
                mDebugView.accept(docInfo);
            }
        }
    }

    @VisibleForTesting
    public void onDocumentMetadataLoaded(String displayName, Bundle bundle) {
        Bundle exif = bundle.getBundle(DocumentsContract.METADATA_EXIF);
        if (exif != null) {
            showExifData(displayName, exif);
        }
        mMetadata.setVisible(exif != null);
    }

    /**
     * Updates a files metadata to the view.
     * @param docName - the name of the doc. needed for launching a geo intent.
     * @param args - bundle of metadata.
     * @param bundle - bundle of metadata.
     */
    @VisibleForTesting
    public void updateMetadata(String docName, Bundle args) {

        mMetadata.setTitle(R.string.inspector_metadata_section);
    public void showExifData(String docName, Bundle bundle) {
        mMetadata.setTitle(R.string.inspector_exif_section);

        if (args.containsKey(ExifInterface.TAG_IMAGE_WIDTH)
            && args.containsKey(ExifInterface.TAG_IMAGE_LENGTH)) {
            int width = args.getInt(ExifInterface.TAG_IMAGE_WIDTH);
            int height = args.getInt(ExifInterface.TAG_IMAGE_LENGTH);
        if (bundle.containsKey(ExifInterface.TAG_IMAGE_WIDTH)
            && bundle.containsKey(ExifInterface.TAG_IMAGE_LENGTH)) {
            int width = bundle.getInt(ExifInterface.TAG_IMAGE_WIDTH);
            int height = bundle.getInt(ExifInterface.TAG_IMAGE_LENGTH);
            mMetadata.put(R.string.metadata_dimensions, String.valueOf(width) + " x "
                + String.valueOf(height));
        }

        if (args.containsKey(ExifInterface.TAG_GPS_LATITUDE)
            && args.containsKey(ExifInterface.TAG_GPS_LONGITUDE) ) {
            double latitude = args.getDouble(ExifInterface.TAG_GPS_LATITUDE);
            double longitude = args.getDouble(ExifInterface.TAG_GPS_LONGITUDE);
        if (bundle.containsKey(ExifInterface.TAG_GPS_LATITUDE)
                && bundle.containsKey(ExifInterface.TAG_GPS_LONGITUDE) ) {
            double latitude = bundle.getDouble(ExifInterface.TAG_GPS_LATITUDE);
            double longitude = bundle.getDouble(ExifInterface.TAG_GPS_LONGITUDE);

            Intent intent = createGeoIntent(latitude, longitude, docName);

@@ -214,28 +234,28 @@ public final class InspectorController {
            }
        }

        if (args.containsKey(ExifInterface.TAG_GPS_ALTITUDE)) {
            double altitude = args.getDouble(ExifInterface.TAG_GPS_ALTITUDE);
        if (bundle.containsKey(ExifInterface.TAG_GPS_ALTITUDE)) {
            double altitude = bundle.getDouble(ExifInterface.TAG_GPS_ALTITUDE);
            mMetadata.put(R.string.metadata_altitude, String.valueOf(altitude));
        }

        if (args.containsKey(ExifInterface.TAG_MAKE)) {
            String make = args.getString(ExifInterface.TAG_MAKE);
        if (bundle.containsKey(ExifInterface.TAG_MAKE)) {
            String make = bundle.getString(ExifInterface.TAG_MAKE);
            mMetadata.put(R.string.metadata_make, make);
        }

        if (args.containsKey(ExifInterface.TAG_MODEL)) {
            String model = args.getString(ExifInterface.TAG_MODEL);
        if (bundle.containsKey(ExifInterface.TAG_MODEL)) {
            String model = bundle.getString(ExifInterface.TAG_MODEL);
            mMetadata.put(R.string.metadata_model, model);
        }

        if (args.containsKey(ExifInterface.TAG_APERTURE)) {
            String aperture = String.valueOf(args.get(ExifInterface.TAG_APERTURE));
        if (bundle.containsKey(ExifInterface.TAG_APERTURE)) {
            String aperture = String.valueOf(bundle.get(ExifInterface.TAG_APERTURE));
            mMetadata.put(R.string.metadata_aperture, aperture);
        }

        if (args.containsKey(ExifInterface.TAG_SHUTTER_SPEED_VALUE)) {
            String shutterSpeed = String.valueOf(args.get(ExifInterface.TAG_SHUTTER_SPEED_VALUE));
        if (bundle.containsKey(ExifInterface.TAG_SHUTTER_SPEED_VALUE)) {
            String shutterSpeed = String.valueOf(bundle.get(ExifInterface.TAG_SHUTTER_SPEED_VALUE));
            mMetadata.put(R.string.metadata_shutter_speed, shutterSpeed);
        }
    }
@@ -327,12 +347,28 @@ public final class InspectorController {
         * Deletes all loader id's when android lifecycle ends.
         */
        void reset();

        /**
         * @param uri
         * @param callback
         */
        void getDocumentMetadata(Uri uri, Consumer<Bundle> callback);
    }

    /**
     * This interface is for unit testing.
     */
    public interface ActionDisplay {
    public interface Display {
        /**
         * Makes the action visible.
         */
        void setVisible(boolean visible);
    }

    /**
     * This interface is for unit testing.
     */
    public interface ActionDisplay extends Display {

        /**
         * Initializes the view based on the action.
@@ -341,11 +377,6 @@ public final class InspectorController {
         */
        void init(Action action, OnClickListener listener);

        /**
         * Makes the action visible.
         */
        void setVisible(boolean visible);

        void setActionHeader(String header);

        void setAppIcon(Drawable icon);
@@ -368,7 +399,7 @@ public final class InspectorController {
    /**
     * Displays a table of image metadata.
     */
    public interface TableDisplay {
    public interface TableDisplay extends Display {

        /**
         * Sets the title of the data.
Loading