Easy Tutorial
❮ Android Tutorial Socket1 Programmer 520 ❯

4.4.2 Further Exploration of ContentProvider — Document Provider

Category Android Basic Tutorial

Introduction to This Section:

>

After completing the previous section, I believe you already know how to use the system-provided ContentProvider or customize your own ContentProvider. It basically meets the needs of daily development. Interestingly, I saw these other providers in the official documentation:

Calendar Provider: The calendar provider is a resource library for calendar-related events. Through its provided API, we can perform some add, delete, modify, and query operations on calendars, time, meetings, reminders, and more! Contacts Provider: The contact provider, needless to say, is the most used one ~ I will translate this article later if I have time! Storage Access Framework (SAF): Storage Access Framework, a new feature introduced after 4.4, provides convenience for users to browse the storage content on their mobile phones. The content that can be accessed includes not only documents, pictures, videos, audio, and downloads but also all content provided by specific ContentProviders (with agreed-upon APIs). Regardless of where the content comes from or which application calls the command to browse the system file content, the system will use a unified interface for you to browse.

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("image/*");
startActivity(intent);

The one on the right is the new feature brought to us by 4.4. It can be used when we generally need to obtain the file URL.

Next, let's briefly go through the documentation~


2. Briefly Go Through the Documentation:

1) Composition of the SAF Framework:

>

-Document provider: A special ContentProvider that allows a storage service (such as Google Drive) to display the files it manages to the outside world. It is a subclass of DocumentsProvider, and in addition, the storage format of the document provider is consistent with the traditional file storage format. As for how you store your content, it is entirely up to you. The Android system has built-in several such Document providers, such as those for downloads, pictures, and videos!

-Client app: An ordinary client software that can receive content returned from the Document provider by triggering ACTION_OPEN_DOCUMENT and/or ACTION_CREATE_DOCUMENT, such as selecting a picture and then returning a Uri.

-Picker: An interface similar to a file manager, and it is a system-level interface that provides access to the Document provider content filtered by the client, which is the DocumentsUI program mentioned!

Some features:

>


2) Overview:

The core of SAF is the implementation of the subclass of DocumentsProvider, which is still a ContentProvider. In a document provider, the data is organized in a traditional file directory tree:

3) Flowchart:

As mentioned above, the document provider data is based on the traditional file hierarchy, but that is just the external form of expression. How to store your data depends on yourself, as long as you can access the overseas interface through the DocumentsProvider API. The following flowchart shows a possible structure of a photo application using SAF:

Analysis:

>

From the above figure, we can see that the Picker is a bridge connecting the caller and the content provider! It provides and tells the caller which content providers can be selected, such as the DriveDocProvider, UsbDocProvider, and CloundDocProvider here. The ACTIONOPENDOCUMENT or ACTIONCREATEDOCUMENT Intent will have the above interaction.

Of course, we can also add filtering conditions in the Intent, such as limiting the MIME type to "image"!

That's all the above, if you have installed other image viewing software, you will also see it here!

In simple terms: After the client sends the above two types of Action Intents, the Picker UI will open, displaying the relevant available Document Providers for the user to choose from, and after the user selects, they can obtain the relevant file information!


4) Client Call and Obtain the Returned Uri

Implementation code is as follows:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private static final int READ_REQUEST_CODE = 42;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button
The content above only tells you what you can know through a Uri, and the acquisition of the Uri is obtained through SAF!

---

### 8) Creating new files and deleting files:

**Creating files:**

```java
private void createFile(String mimeType, String fileName) {
    Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    intent.setType(mimeType);
    intent.putExtra(Intent.EXTRA_TITLE, fileName);
    startActivityForResult(intent, WRITE_REQUEST_CODE);
}

You can obtain the Uri of the created file in onActivityResult()

Deleting files:

The premise is that Document.COLUMN_FLAGS contains **SUPPORTS_DELETE**

DocumentsContract.deleteDocument(getContentResolver(), uri);

9) Writing a custom Document Provider

If you want the data of your own application to be opened in the documentsui, you need to write your own document provider. The following introduces the steps to customize DocumentsProvider:

>

Here is an example of how to write a Provider:

<manifest... >
    ...
    <uses-sdk
        android:minSdkVersion="19"
        android:targetSdkVersion="19" />
    ....
    <provider
        android:name="com.example.android.storageprovider.MyCloudProvider"
        android:authorities="com.example.android.storageprovider.documents"
        android:grantUriPermissions="true"
        android:exported="true"
        android:permission="android.permission.MANAGE_DOCUMENTS"
        android:enabled="@bool/atLeastKitKat">
        <intent-filter>
            <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
        </intent-filter>
    </provider>
</application>

</manifest>

10) Subclasses of DocumentsProvider

At least implement the following methods:

>

There are other methods, but they are not required. The following demonstrates a rough implementation of accessing the file (file) system DocumentsProvider.

Implement queryRoots

@Override
public Cursor queryRoots(String[] projection) throws FileNotFoundException {

    // Create a cursor with either the requested fields, or the default
    // projection if "projection" is null.
    final MatrixCursor result =
            new MatrixCursor(resolveRootProjection(projection));

    // If user is not logged in, return an empty root cursor.  This removes our
    // provider from the list entirely.
    if (!isUserLoggedIn()) {
        return result;
    }

    // It's possible to have multiple roots (e.g. for multiple accounts in the
    // same app) -- just add multiple cursor rows.
    // Construct one row for a root called "MyCloud".
    final MatrixCursor.RowBuilder row = result.newRow();
    row.add(Root.COLUMN_ROOT_ID, ROOT);
    row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.root_summary));

    // FLAG_SUPPORTS_CREATE means at least one directory under the root supports
    // creating documents. FLAG_SUPPORTS_RECENTS means your application's most
    // recently used documents will show up in the "Recents" category.
    // FLAG_SUPPORTS_SEARCH allows users to search all documents the application
    // shares.
    row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE |
            Root.FLAG_SUPPORTS_RECENTS |
            Root.FLAG_SUPPORTS_SEARCH);

    // COLUMN_TITLE is the root title (e.g. Gallery, Drive).
    row.add(Root.COLUMN_TITLE, getContext().getString(R.string.title));

    // This document id cannot change once it's shared.
    row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(mBaseDir));

    // The child MIME types are used to filter the roots and only present to the
    //  user roots that contain the desired type somewhere in their file hierarchy.
    row.add(Root.COLUMN_MIME_TYPES, getChildMimeTypes(mBaseDir));
    row.add(Root.COLUMN_AVAILABLE_BYTES, mBaseDir.getFreeSpace());
    row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);

    return result;
}

Implement queryChildDocuments

public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
                              String sortOrder) throws FileNotFoundException {

    final MatrixCursor result = new
            MatrixCursor(resolveDocumentProjection(projection));
    final File parent = getFileForDocId(parentDocumentId);
    for (File file : parent.listFiles()) {
        // Adds the file's display name, MIME
If it's an earlier version: the URI might look like this:


content://media/external/images/media/image%3A69983


Here's a comprehensive solution I found elsewhere, original article link: [Android 4.4 Issues with Obtaining Resource Paths](http://blog.csdn.net/huangyanan1989/article/details/17263203)


public static String getPath(final Context context, final Uri uri) { final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; // DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { // ExternalStorageProvider
if (isExternalStorageDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0];

        if ("primary".equalsIgnoreCase(type)) {
            return Environment.getExternalStorageDirectory() + "/" + split[1];
        }

        // TODO handle non-primary volumes  
    }
    // DownloadsProvider  
    else if (isDownloadsDocument(uri)) {

        final String id = DocumentsContract.getDocumentId(uri);
        final Uri contentUri = ContentUris.withAppendedId(
                Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

        return getDataColumn(context, contentUri, null, null);
    }
    // MediaProvider  
    else if (isMediaDocument(uri)) {
        final String docId = DocumentsContract.getDocumentId(uri);
        final String[] split = docId.split(":");
        final String type = split[0];
        Uri contentUri = null;
        if ("image".equals(type)) {
            contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        } else if ("video".equals(type)) {
            contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
        } else if ("audio".equals(type)) {
            contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
        }
        final String selection = "_id=?";
        final String[] selectionArgs = new String[] {
                split[1]
        };
        return getDataColumn(context, contentUri, selection, selectionArgs);
    }
}
// MediaStore (and general)  
else if ("content".equalsIgnoreCase(uri.getScheme())) {
    return getDataColumn(context, uri, null, null);
}
// File  
else if ("file".equalsIgnoreCase(uri.getScheme())) {
    return uri.getPath();
}
return null;

}

/**

Cursor cursor = null;
final String column = "_data";
final String[] projection = {
        column
};

try {
    cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
            null);
    if (cursor != null && cursor.moveToFirst()) {
        final int column_index = cursor.getColumnIndexOrThrow(column);
        return cursor.getString(column_index);
    }
} finally {
    if (cursor != null)
        cursor.close();
}
return null;

}

/**

/**

/**

Summary of This Section:

>

Alright, that's it for this section on the Android Storage Access Framework (SAF). There aren't many examples, but I'll delve deeper into it later when I need to use it. It's good to know that obtaining file paths has become simpler since Android 4.4.

-1.0 Android Basic Tutorial for Beginners

-1.0.1 Latest Android Basic Tutorial Table of Contents for 2015

-[

❮ Android Tutorial Socket1 Programmer 520 ❯