Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
2 changes: 2 additions & 0 deletions mediacontroller/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.example.android.mediacontroller">

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
Expand Down
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 Down Expand Up @@ -117,6 +119,9 @@ public class MediaAppControllerActivity extends AppCompatActivity {
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 STORAGE_PERMISSION_REQUEST = 1;

private MediaAppDetails mMediaAppDetails;
private MediaControllerCompat mController;
private MediaBrowserCompat mBrowser;
Expand Down Expand Up @@ -167,6 +172,8 @@ protected void onCreate(Bundle savedInstanceState) {
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
toolbar.setNavigationOnClickListener(v -> finish());
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
ActivityCompat.requestPermissions(MediaAppControllerActivity.this, permissions, STORAGE_PERMISSION_REQUEST);

mViewPager = findViewById(R.id.view_pager);
mInputTypeView = findViewById(R.id.input_type);
Expand Down Expand Up @@ -248,20 +255,21 @@ 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 +279,31 @@ public Object instantiateItem(@NonNull ViewGroup container, int position) {
});
}


@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode,
permissions,
grantResults);

if (requestCode == STORAGE_PERMISSION_REQUEST) {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(getApplicationContext(),
"Storage Permission Granted, can save browse tree.",
Toast.LENGTH_SHORT)
.show();
} else {
Toast.makeText(getApplicationContext(),
"Storage Permission Denied, can not save browse tree to file.",
Toast.LENGTH_SHORT)
.show();
}
}
}

@Override
protected void onDestroy() {
if (mController != null) {
Expand Down Expand Up @@ -813,8 +846,8 @@ private static class AudioFocusHelper
private final Spinner mFocusTypeSpinner;

private AudioFocusHelper(@NonNull Context context,
@NonNull ToggleButton focusToggleButton,
@NonNull Spinner focusTypeSpinner) {
@NonNull ToggleButton focusToggleButton,
@NonNull Spinner focusTypeSpinner) {

mAudioManager = (AudioManager) context.getSystemService(AUDIO_SERVICE);
mToggleButton = focusToggleButton;
Expand Down Expand Up @@ -1102,7 +1135,8 @@ private class BrowseMediaItemsAdapter extends
RecyclerView.Adapter<BrowseMediaItemsAdapter.ViewHolder> {

private List<MediaBrowserCompat.MediaItem> mItems;
private Stack<String> mNodes = new Stack<>();
private final Stack<String> mNodes = new Stack<>();
private MediaBrowseTreeSnapshot mMediaBrowseTreeSnapshot;

MediaBrowserCompat.SubscriptionCallback callback =
new MediaBrowserCompat.SubscriptionCallback() {
Expand All @@ -1122,7 +1156,7 @@ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
}

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
public void onBindViewHolder(@NonNull ViewHolder holder, int position){
if (mNodes.size() == 0) {
holder.name.setText(getString(R.string.media_no_browser));
holder.name.setVisibility(View.VISIBLE);
Expand Down Expand Up @@ -1150,7 +1184,6 @@ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
});
return;
}

final MediaBrowserCompat.MediaItem item = mItems.get(position);
holder.name.setText(item.getDescription().getTitle());
holder.name.setVisibility(View.VISIBLE);
Expand Down Expand Up @@ -1206,7 +1239,7 @@ void updateItems(List<MediaBrowserCompat.MediaItem> 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 +1261,27 @@ void init(View topButtonView, View upButtonView) {
}
});
}
if (saveButtonView != null) {
saveButtonView.setOnClickListener(v -> {
if(mMediaBrowseTreeSnapshot != null) {
mMediaBrowseTreeSnapshot.takeBrowserSnapshot();
}
else if(mBrowser != null) {
mMediaBrowseTreeSnapshot = new MediaBrowseTreeSnapshot(mBrowser, getApplicationContext());
mMediaBrowseTreeSnapshot.takeBrowserSnapshot();
}
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 @@ -1311,4 +1365,4 @@ protected void unsubscribe() {
super.unsubscribe();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public void bindTo(RecyclerView.ViewHolder vh) {
holder.appIconView.getContext().getString(R.string.app_icon_desc,
appDetails.appName));
holder.appNameView.setText(appDetails.appName);
holder.appPackageView.setText(appDetails.packageName);
//holder.appPackageView.setText(appDetails.packageName);

holder.controlButton.setOnClickListener(view ->
appSelectedListener.onMediaAppClicked(appDetails, false));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package com.example.android.mediacontroller

import android.content.Context
import android.os.Environment
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 java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.PrintWriter
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.Semaphore

class MediaBrowseTreeSnapshot(private val mBrowser: MediaBrowserCompat, private val mContext: Context) {
Copy link
Member

Choose a reason for hiding this comment

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

Kotlin doesn't use 'm' prefixes in parameters or member variables.

nit: Ideally context should be the first parameter

Copy link
Author

Choose a reason for hiding this comment

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

Done.

private val TAG = "MediaBrowseTreeSnapshot"

fun takeBrowserSnapshot() {
val loaded = Semaphore(1)
val executorService = Executors.newFixedThreadPool(4)
val mItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
executorService.execute {
Copy link
Member

Choose a reason for hiding this comment

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

Since this is in Kotlin, you should use suspend functions, not executors, to do the background work.

Copy link
Author

Choose a reason for hiding this comment

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

Done.

try {
loaded.acquire()
} catch (e: InterruptedException) {
e.printStackTrace()
}
mBrowser.subscribe(mBrowser.root, object : SubscriptionCallback() {
override fun onChildrenLoaded(parentId: String,
children: List<MediaBrowserCompat.MediaItem>) {
// Notify the main thread that all of the children have loaded
Log.i(TAG, "Children loaded for init")
mItems.addAll(children)
loaded.release()

super.onChildrenLoaded(parentId, children)
}
})

// Wait for all of the media children to be loaded before starting snapshot
try {
loaded.acquire()
} catch (e: InterruptedException) {
e.printStackTrace()
}

if (mItems.size > 0) {
initDFSOnBrowseTree(mItems, executorService)
} else {
notifyUser("No media items found, could not save tree.")
}
}
}

private fun initDFSOnBrowseTree(mItems: MutableList<MediaBrowserCompat.MediaItem>, executorService: ExecutorService) {
val root = Environment.getExternalStorageDirectory()
val dirsPath = root.absolutePath + "/Temp/"
val dirs = File(dirsPath)
dirs.mkdirs()
val file = File(dirs.absolutePath,
"_BrowseTreeContent.txt")
if (file.exists()) {
file.delete()
}
try {
val f = FileOutputStream(file)
val pw = PrintWriter(f)
runDFSOnBrowseTree(mItems, executorService, pw)
pw.flush()
pw.close()
f.close()
} catch (e: IOException) {
e.printStackTrace()
}
notifyUser("MediaItems saved to " +
file.absolutePath)
}

private fun runDFSOnBrowseTree(mItems: MutableList<MediaBrowserCompat.MediaItem>, executorService: ExecutorService, printWriter: PrintWriter) {
printWriter.println("Root:")
val writeCompleted = Semaphore(1)
executorService.execute {
for (item in mItems) {
try {
writeCompleted.acquire()
} catch (e: InterruptedException) {
e.printStackTrace()
}
visitMediaItemNode(item, printWriter, 1,
executorService)
writeCompleted.release()
}
}
}

private fun visitMediaItemNode(mediaItem: MediaBrowserCompat.MediaItem?, printWriter: PrintWriter, depth: Int,
executorService: ExecutorService) {
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 loaded = Semaphore(1)
try {
loaded.acquire()
} catch (e: InterruptedException) {
e.printStackTrace()
}
val mChildren: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
executorService.execute {
mBrowser.subscribe(mid,
object : SubscriptionCallback() {
override fun onChildrenLoaded(parentId: String,
children: List<MediaBrowserCompat.MediaItem>) {
// Notify the main thread that all of the children have loaded
mChildren.addAll(children)
loaded.release()
super.onChildrenLoaded(parentId, children)
}
})
}

// Wait for all of the media children to be loaded before continuing DFS
try {
loaded.acquire()
} catch (e: InterruptedException) {
e.printStackTrace()
}

// Run visit on all of the nodes children
for (mediaItemChild in mChildren) {
visitMediaItemNode(mediaItemChild, printWriter, depth + 1,
executorService)
}
}
}
}

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)
}

private fun notifyUser(textToNotify: String) {
Handler(Looper.getMainLooper()).post {
val toast = Toast.makeText(
mContext,
textToNotify,
Toast.LENGTH_LONG)
toast.setMargin(50f, 50f)
toast.show()
}
}
}
2 changes: 2 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,8 @@
android:layout_height="wrap_content"
android:text="@string/media_browse_tree_up" />

<Button android:id="@+id/media_browse_tree_save" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/media_browse_tree_save"/>

</LinearLayout>

<TextView
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 @@
<string name="media_no_browser">No Browser</string>
<string name="media_browse_tree_loading">Loading…</string>
<string name="media_browse_tree_empty">Empty.</string>
<string name="media_browse_tree_save">Save to file</string>
<string name="search_media">Search</string>

<string name="rating_thumb_up">Thumb Up</string>
Expand Down