Skip to content

[Infra] Adding a button to snapshot the current Media Browse Tree #28

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Dec 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mediacontroller/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,5 @@ dependencies {
implementation "androidx.leanback:leanback:$leanback_version"
implementation 'com.android.support:design:28.0.0'
implementation "com.google.android.material:material:$material_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import static androidx.media.MediaBrowserServiceCompat.BrowserRoot.EXTRA_SUGGESTED;
import static java.util.Arrays.asList;

import android.Manifest;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
Expand Down Expand Up @@ -61,6 +62,7 @@
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.graphics.drawable.DrawableCompat;
Expand All @@ -73,6 +75,8 @@

import com.google.android.material.tabs.TabLayout;

import java.io.FileNotFoundException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
Expand Down Expand Up @@ -111,12 +115,16 @@ public class MediaAppControllerActivity extends AppCompatActivity {
// Key name for Intent extras.
private static final String APP_DETAILS_EXTRA =
"com.example.android.mediacontroller.APP_DETAILS_EXTRA";
private static final String DEFAULT_BROWSE_TREE_FILE_NAME = "_BrowseTreeContent.txt";

// Index values for spinner.
private static final int SEARCH_INDEX = 0;
private static final int MEDIA_ID_INDEX = 1;
private static final int URI_INDEX = 2;

// Used for user storage permission request
private static final int CREATE_DOCUMENT_REQUEST_FOR_SNAPSHOT = 1;

private MediaAppDetails mMediaAppDetails;
private MediaControllerCompat mController;
private MediaBrowserCompat mBrowser;
Expand All @@ -143,6 +151,8 @@ public class MediaAppControllerActivity extends AppCompatActivity {

private ViewGroup mRatingViewGroup;

private MediaBrowseTreeSnapshot mMediaBrowseTreeSnapshot;

private final SparseArray mActionButtonMap = new SparseArray<>();

/**
Expand Down Expand Up @@ -248,20 +258,20 @@ public Object instantiateItem(@NonNull ViewGroup container, int position) {
browseTreeList.setHasFixedSize(true);
browseTreeList.setAdapter(mBrowseMediaItemsAdapter);
mBrowseMediaItemsAdapter.init(findViewById(R.id.media_browse_tree_top),
findViewById(R.id.media_browse_tree_up));
findViewById(R.id.media_browse_tree_up), findViewById(R.id.media_browse_tree_save));

final RecyclerView browseTreeListExtraSuggested = findViewById(R.id.media_items_list_extra_suggested);
browseTreeListExtraSuggested.setLayoutManager(new LinearLayoutManager(this));
browseTreeListExtraSuggested.setHasFixedSize(true);
browseTreeListExtraSuggested.setAdapter(mBrowseMediaItemsExtraSuggestedAdapter);
mBrowseMediaItemsExtraSuggestedAdapter.init(findViewById(R.id.media_browse_tree_top_extra_suggested),
findViewById(R.id.media_browse_tree_up_extra_suggested));
findViewById(R.id.media_browse_tree_up_extra_suggested), findViewById(R.id.media_browse_tree_save));

final RecyclerView searchItemsList = findViewById(R.id.search_items_list);
searchItemsList.setLayoutManager(new LinearLayoutManager(this));
searchItemsList.setHasFixedSize(true);
searchItemsList.setAdapter(mSearchMediaItemsAdapter);
mSearchMediaItemsAdapter.init(null, null);
mSearchMediaItemsAdapter.init(null, null, null);

findViewById(R.id.search_button).setOnClickListener(v -> {
CharSequence queryText = ((TextView) findViewById(R.id.search_query)).getText();
Expand All @@ -271,6 +281,28 @@ public Object instantiateItem(@NonNull ViewGroup container, int position) {
});
}

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == CREATE_DOCUMENT_REQUEST_FOR_SNAPSHOT) {
if (resultCode == RESULT_OK && mMediaBrowseTreeSnapshot != null) {
Uri uri = data.getData();
OutputStream outputStream = null;
try {
outputStream = getContentResolver().openOutputStream(uri);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
mMediaBrowseTreeSnapshot.takeBrowserSnapshot(outputStream);
Toast.makeText(this, "Output file location: " + uri.getPath(), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "File could not be saved.", Toast.LENGTH_SHORT).show();
}


}
}

@Override
protected void onDestroy() {
if (mController != null) {
Expand Down Expand Up @@ -1102,7 +1134,7 @@ private class BrowseMediaItemsAdapter extends
RecyclerView.Adapter {

private List mItems;
private Stack mNodes = new Stack<>();
private final Stack mNodes = new Stack<>();

MediaBrowserCompat.SubscriptionCallback callback =
new MediaBrowserCompat.SubscriptionCallback() {
Expand Down Expand Up @@ -1206,7 +1238,7 @@ void updateItems(List items) {
* Assigns click handlers to the buttons if provided for moving to the top of the tree or
* for moving up one level in the tree.
*/
void init(View topButtonView, View upButtonView) {
void init(View topButtonView, View upButtonView, View saveButtonView) {
if (topButtonView != null) {
topButtonView.setOnClickListener(v -> {
if (mNodes.size() > 1) {
Expand All @@ -1228,6 +1260,38 @@ void init(View topButtonView, View upButtonView) {
}
});
}
if (saveButtonView != null) {
saveButtonView.setOnClickListener(v -> {
takeMediaBrowseTreeSnapshot();
});
}

}

private void takeMediaBrowseTreeSnapshot(){
if(mBrowser != null) {
if(mMediaBrowseTreeSnapshot == null) {
mMediaBrowseTreeSnapshot = new MediaBrowseTreeSnapshot(
MediaAppControllerActivity.this, mBrowser);
}
Intent saveTextFileIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
saveTextFileIntent.addCategory(Intent.CATEGORY_OPENABLE);
saveTextFileIntent.setType("text/plain");
saveTextFileIntent.putExtra(
Intent.EXTRA_TITLE, DEFAULT_BROWSE_TREE_FILE_NAME);
MediaAppControllerActivity.this.startActivityForResult(saveTextFileIntent,
CREATE_DOCUMENT_REQUEST_FOR_SNAPSHOT);

}else{
Log.e(TAG, "Media browser is null");
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(),"No media browser to snapshot",
Toast.LENGTH_SHORT).show();
}
});
}
}

protected void subscribe() {
Expand Down Expand Up @@ -1284,7 +1348,8 @@ private class SearchMediaItemsAdapter extends BrowseMediaItemsAdapter {
@Override
protected void subscribe() {
if (treeDepth() == 1) {
mBrowser.search(getCurrentNode(), null, new MediaBrowserCompat.SearchCallback() {
mBrowser.search(getCurrentNode(), null,
new MediaBrowserCompat.SearchCallback() {
@Override
public void onSearchResult(@NonNull String query, Bundle extras,
@NonNull List items) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package com.example.android.mediacontroller

import android.content.Context
import android.os.Handler
import android.os.Looper
import android.support.v4.media.MediaBrowserCompat
import android.support.v4.media.MediaBrowserCompat.SubscriptionCallback
import android.util.Log
import android.widget.Toast
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import java.io.OutputStream
import java.io.PrintWriter
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine


class MediaBrowseTreeSnapshot(private val context: Context, private val browser: MediaBrowserCompat):ViewModel() {
private val TAG = "MediaBrowseTreeSnapshot"


/**
* Loads the browsers top level children and runs a DFS on them printing out
* each media item's contentes as it is visited.
*/
fun takeBrowserSnapshot(outputStream: OutputStream) {

viewModelScope.launch {
val mediaItems: MutableList = getChildNodes(browser.root)
if (mediaItems.isNotEmpty()) {
runDFSOnBrowseTree(mediaItems, outputStream)
for (item in mediaItems) {
Log.i(TAG, item.toString())
}
} else {
notifyUser("No media items found, could not save tree.")
}
}
}

private suspend fun getChildNodes(rootItemMid: String): MutableList =
suspendCoroutine {
val mediaItems: MutableList = ArrayList()
browser.subscribe(rootItemMid, object : SubscriptionCallback() {
override fun onChildrenLoaded(parentId: String,
children: List) {
// Notify the main thread that all of the children have loaded
mediaItems.addAll(children)
super.onChildrenLoaded(parentId, children)
it.resume(mediaItems)
}
})
}

/**
* Kicks off the browse tree depth first search by visiting all of the top level media
* item nodes.
*/
private suspend fun runDFSOnBrowseTree(mediaItems: MutableList, outputStream: OutputStream) {
val printWriter = PrintWriter(outputStream)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider whether this bit (the output part) should be wrapped in a withContext(Dispatchers.IO) block.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good! I will merge this then update the functionality soon.

printWriter.println("Root:")
for (item in mediaItems) {
visitMediaItemNode(item, printWriter, 1)
}
printWriter.flush()
printWriter.close()
outputStream.close()
notifyUser("MediaItems saved to specified location.")
}

/**
* Visits a media item node by printing out its contents and then visiting all of its children.
*/
private suspend fun visitMediaItemNode(mediaItem: MediaBrowserCompat.MediaItem?, printWriter: PrintWriter, depth: Int) {
if (mediaItem != null) {
printMediaItemDescription(printWriter, mediaItem, depth)
val mid = if (mediaItem.mediaId != null) mediaItem.mediaId!! else ""

// If a media item is not a leaf continue DFS on it
if (mediaItem.isBrowsable && mid != "") {

val mediaChildren: MutableList = getChildNodes(mid)

// Run visit on all of the nodes children
for (mediaItemChild in mediaChildren) {
visitMediaItemNode(mediaItemChild, printWriter, depth + 1)
Log.i(TAG, "Visiting:" + mediaItemChild.toString())
}
}
}
}

/**
* Prints the contents of a media item using a print writer.
*/
private fun printMediaItemDescription(printWriter: PrintWriter, mediaItem: MediaBrowserCompat.MediaItem, depth: Int) {
val descriptionCompat = mediaItem.description
// Tab the media item to the respective depth
val tabStr = String(CharArray(depth)).replace("\u0000",
"\t")
val titleStr = if (descriptionCompat.title != null) descriptionCompat.title.toString() else "NAN"
val subTitleStr = if (descriptionCompat.subtitle != null) descriptionCompat.subtitle.toString() else "NAN"
val mIDStr = if (descriptionCompat.mediaId != null) descriptionCompat.mediaId else "NAN"
val uriStr = if (descriptionCompat.mediaUri != null) descriptionCompat.mediaUri.toString() else "NAN"
val desStr = if (descriptionCompat.description != null) descriptionCompat.description.toString() else "NAN"
val infoStr = String.format(
"%sTitle:%s,Subtitle:%s,MediaId:%s,URI:%s,Description:%s",
tabStr, titleStr, subTitleStr, mIDStr, uriStr, desStr)
printWriter.println(infoStr)
}

/**
* Display formatted toast to user.
*/
private fun notifyUser(textToNotify: String) {
Handler(Looper.getMainLooper()).post {
val toast = Toast.makeText(
context,
textToNotify,
Toast.LENGTH_LONG)
toast.setMargin(50f, 50f)
toast.show()
}
}
}
5 changes: 5 additions & 0 deletions mediacontroller/src/main/res/layout/media_browse_tree.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
android:layout_height="wrap_content"
android:text="@string/media_browse_tree_up" />

android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/media_browse_tree_save"/>


Expand Down
1 change: 1 addition & 0 deletions mediacontroller/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
No Browser
Loading…
Empty.
Save to file
Search

Thumb Up
Expand Down