OSDN Git Service

Fix Issue 5547: Apollo will not play MP3 files when using Root Explorer
authorJorge Ruesga <jorge@ruesga.com>
Fri, 20 Jul 2012 22:56:44 +0000 (00:56 +0200)
committerGerrit Code Review <gerrit@review.cyanogenmod.com>
Sun, 19 Aug 2012 10:09:06 +0000 (14:09 +0400)
Apollo doesn't have an activity registered for support "file" schemas, that are used by file
managers when using the method setDataAndType of Intent class.

This changes adds a new activity (PlayExternal) that registers this kind of schema, and pass
the request to AudioPlayerHolder. Allow direct play the file, or enqueue in the current track list

Patch 2: Remove trailing white-spaces
         Rename removeAllTrack to removeAllTracks

Change-Id: I25b728700bd0d2d58d31e4de4bed81ee3e368e0a

AndroidManifest.xml
res/values/strings.xml
res/values/styles.xml
src/com/andrew/apollo/IApolloService.aidl
src/com/andrew/apollo/activities/AudioPlayerHolder.java
src/com/andrew/apollo/activities/PlayExternal.java [new file with mode: 0755]
src/com/andrew/apollo/adapters/PagerAdapter.java
src/com/andrew/apollo/list/fragments/TracksFragment.java
src/com/andrew/apollo/service/ApolloService.java
src/com/andrew/apollo/utils/MusicUtils.java
src/com/andrew/apollo/utils/RefreshableFragment.java [new file with mode: 0644]

index f4633bb..b8c4989 100644 (file)
                 <category android:name="android.intent.category.DEFAULT" />\r
             </intent-filter>\r
         </activity>\r
+        <!-- Play External File -->\r
+        <activity\r
+            android:name=".activities.PlayExternal"\r
+            android:clearTaskOnLaunch="true"\r
+            android:excludeFromRecents="true"\r
+            android:noHistory="true"\r
+            android:launchMode="singleTask"\r
+            android:theme="@style/Theme.Light.Translucent"\r
+            android:label="@string/app_name" >\r
+            <intent-filter>\r
+                <action android:name="android.intent.action.VIEW" />\r
+\r
+                <category android:name="android.intent.category.DEFAULT" />\r
+\r
+                <data android:scheme="file" />\r
+                <data android:mimeType="audio/*" />\r
+                <data android:mimeType="application/ogg" />\r
+                <data android:mimeType="application/x-ogg" />\r
+                <data android:mimeType="application/itunes" />\r
+            </intent-filter>\r
+        </activity>\r
         <!-- Track browser -->\r
         <activity\r
             android:name=".activities.TracksBrowser"\r
index 48ce696..3c103d8 100644 (file)
     <string name="tab_playlists">PLAYLISTS</string>\r
     <string name="tab_genres">GENRES</string>\r
 \r
+    <!-- External file dialog -->\r
+    <string name="play_external_question_msg">Select the action to apply to: \n\n<b><xliff:g id="name">%s</xliff:g></b></string>\r
+    <string name="play_external_question_button_play">Play</string>\r
+    <string name="play_external_question_button_queue">Enqueue</string>\r
+    <string name="play_external_question_button_cancel">Cancel</string>\r
+    <string name="play_external_error">Failed to play file</string>\r
+\r
     <!-- Something went wrong -->\r
     <string name="error">Error</string>\r
 \r
index b537ff6..294d44e 100644 (file)
@@ -1,6 +1,20 @@
 <?xml version="1.0" encoding="utf-8"?>\r
 <resources>\r
 \r
+    <!-- Translucent dialog (for @PlayExternal) -->\r
+    <style name="Theme.Light.Translucent" parent="android:style/Theme.Light.NoTitleBar">\r
+        <item name="android:windowIsTranslucent">true</item>\r
+        <item name="android:windowBackground">@android:color/transparent</item>\r
+        <item name="android:windowContentOverlay">@null</item>\r
+        <item name="android:windowNoTitle">true</item>\r
+        <item name="android:windowIsFloating">true</item>\r
+        <item name="android:backgroundDimEnabled">false</item>\r
+    </style>\r
+    <style name="Theme.Light.Translucent.Dialog" parent="@android:style/Theme.Holo.Light.Dialog">\r
+        <item name="android:windowBackground">@android:color/transparent</item>\r
+        <item name="android:windowContentOverlay">@null</item>\r
+    </style>\r
+\r
     <!-- Custom tabs -->\r
     <style name="Tabs">\r
         <item name="android:layout_width">wrap_content</item>\r
index b44edef..b3a9b39 100644 (file)
@@ -6,6 +6,7 @@ interface IApolloService
 {\r
     void openFile(String path);\r
     void open(in long [] list, int position);\r
+    long getIdFromPath(String path);\r
     int getQueuePosition();\r
     boolean isPlaying();\r
     void stop();\r
index ef6eee8..3911684 100644 (file)
@@ -61,6 +61,8 @@ public class AudioPlayerHolder extends FragmentActivity implements ServiceConnec
 \r
     private static final int EFFECTS_PANEL = 0;\r
 \r
+    private PagerAdapter mPagerAdapter;\r
+\r
     @Override\r
     protected void onCreate(Bundle icicle) {\r
         // For the theme chooser and overflow MenuItem\r
@@ -91,6 +93,17 @@ public class AudioPlayerHolder extends FragmentActivity implements ServiceConnec
     }\r
 \r
     @Override\r
+    protected void onNewIntent(Intent intent) {\r
+        // If an activity is requesting access to this activity, and\r
+        // the activity is in the stack, the the fragments may need\r
+        // be refreshed. Update the page adapter\r
+        if (mPagerAdapter != null) {\r
+            mPagerAdapter.refresh();\r
+        }\r
+        super.onNewIntent(intent);\r
+    }\r
+\r
+    @Override\r
     public void onServiceConnected(ComponentName name, IBinder obj) {\r
         MusicUtils.mService = IApolloService.Stub.asInterface(obj);\r
     }\r
@@ -264,7 +277,7 @@ public class AudioPlayerHolder extends FragmentActivity implements ServiceConnec
      */\r
     public void initPager() {\r
         // Initiate PagerAdapter\r
-        PagerAdapter mPagerAdapter = new PagerAdapter(getSupportFragmentManager());\r
+        mPagerAdapter = new PagerAdapter(getSupportFragmentManager());\r
         Bundle bundle = new Bundle();\r
         bundle.putString(MIME_TYPE, Audio.Playlists.CONTENT_TYPE);\r
         bundle.putLong(BaseColumns._ID, PLAYLIST_QUEUE);\r
diff --git a/src/com/andrew/apollo/activities/PlayExternal.java b/src/com/andrew/apollo/activities/PlayExternal.java
new file mode 100755 (executable)
index 0000000..c6c87d8
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2012 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.andrew.apollo.activities;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ComponentName;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.support.v4.app.FragmentActivity;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.andrew.apollo.IApolloService;
+import com.andrew.apollo.R;
+import com.andrew.apollo.service.ServiceToken;
+import com.andrew.apollo.utils.MusicUtils;
+
+import java.io.File;
+import java.net.URLDecoder;
+
+/**
+ * An activity that lets external browsers launching music inside Apollo
+ */
+public class PlayExternal extends FragmentActivity
+    implements ServiceConnection, DialogInterface.OnCancelListener {
+
+    private static final String TAG = "PlayExternal";
+
+    private ServiceToken mToken;
+    private Uri mUri;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Get the external file to play
+        Intent intent = getIntent();
+        if (intent == null) {
+            finish();
+            return;
+        }
+        mUri = intent.getData();
+        if (mUri == null) {
+            finish();
+            return;
+        }
+    }
+
+    @Override
+    public void onServiceConnected(ComponentName name, IBinder obj) {
+        MusicUtils.mService = IApolloService.Stub.asInterface(obj);
+        play(this.mUri);
+    }
+
+    @Override
+    public void onServiceDisconnected(ComponentName name) {
+        MusicUtils.mService = null;
+    }
+
+    @Override
+    protected void onStart() {
+        // Bind to Service
+        mToken = MusicUtils.bindToService(this, this);
+        super.onStart();
+    }
+
+    @Override
+    protected void onStop() {
+        // Unbind
+        if (MusicUtils.mService != null)
+            MusicUtils.unbindFromService(mToken);
+        super.onStop();
+    }
+
+    private void play(Uri uri) {
+        try {
+            final String file = URLDecoder.decode( uri.toString(), "UTF-8");
+            final String name = new File(file).getName();
+
+            // Try to resolve the file to a media id
+            final long id = MusicUtils.mService.getIdFromPath(file);
+            if( id == -1 ) {
+                // Open the stream, But we will not have album information
+                openFile(file);
+            }
+            else {
+                // Show a dialog asking the user for play or queue the song
+                AlertDialog.Builder builder =
+                        new AlertDialog.Builder(this, R.style.Theme_Light_Translucent_Dialog);
+                builder.setTitle(R.string.app_name);
+                builder.setMessage(getString(R.string.play_external_question_msg, name));
+
+                DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        try {
+                            switch (which) {
+                                case DialogInterface.BUTTON_POSITIVE:
+                                    playOrEnqueuFile(file, id, false);
+                                    break;
+
+                                case DialogInterface.BUTTON_NEUTRAL:
+                                    playOrEnqueuFile(file, id, true);
+                                    break;
+
+                                case DialogInterface.BUTTON_NEGATIVE:
+                                    break;
+
+                                default:
+                                    break;
+                            }
+                        } finally {
+                            finish();
+                        }
+                    }
+                };
+                builder.setPositiveButton(R.string.play_external_question_button_play, listener);
+                builder.setNeutralButton(R.string.play_external_question_button_queue, listener);
+                builder.setNegativeButton(R.string.play_external_question_button_cancel, listener);
+
+                Dialog dialog = builder.create();
+                dialog.setOnCancelListener(this);
+                dialog.show();
+            }
+
+        } catch (Exception e) {
+            Toast.makeText(
+                    getApplicationContext(),
+                    R.string.play_external_error,
+                    Toast.LENGTH_SHORT);
+            Log.e(TAG, String.format("Failed to play external file: ", uri.toString()), e);
+            try {
+                Thread.sleep(1000L);
+            }catch (Exception e2) {}
+            finish();
+        }
+
+    }
+
+    @Override
+    public void onCancel(DialogInterface dialog) {
+        finish();
+    }
+
+    private void playOrEnqueuFile(String file, long id, boolean enqueue) {
+        final long[] list = new long[] {id};
+        if (!enqueue) {
+            //Remove the actual queue
+            MusicUtils.removeAllTracks();
+            MusicUtils.playAll(getApplicationContext(), list, 0);
+        }
+        else {
+            MusicUtils.addToCurrentPlaylist(getApplicationContext(), list);
+        }
+
+        // Show now playing
+        Intent intent = new Intent(this, AudioPlayerHolder.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
+        startActivity(intent);
+    }
+
+    private void openFile(String file) throws RemoteException {
+        // Stop, load and play
+        MusicUtils.mService.stop();
+        MusicUtils.mService.openFile(file);
+        MusicUtils.mService.play();
+
+        // Show now playing
+        Intent nowPlayingIntent = new Intent(this, AudioPlayerHolder.class);
+        startActivity(nowPlayingIntent);
+    }
+
+}
index 263a875..ca61087 100644 (file)
@@ -10,6 +10,8 @@ import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentManager;\r
 import android.support.v4.app.FragmentPagerAdapter;\r
 \r
+import com.andrew.apollo.utils.RefreshableFragment;\r
+\r
 /**\r
  * @author Andrew Neal\r
  */\r
@@ -36,4 +38,15 @@ public class PagerAdapter extends FragmentPagerAdapter {
         return mFragments.get(position);\r
     }\r
 \r
+    /**\r
+     * This method update the fragments that extends the {@link RefreshableFragment} class\r
+     */\r
+    public void refresh() {\r
+        for (int i = 0; i < mFragments.size(); i++) {\r
+            if( mFragments.get(i) instanceof RefreshableFragment ) {\r
+                ((RefreshableFragment)mFragments.get(i)).refresh();\r
+            }\r
+        }\r
+    }\r
+\r
 }\r
index 3f8e39d..8f46dd1 100644 (file)
@@ -19,7 +19,6 @@ import android.provider.MediaStore.Audio.AudioColumns;
 import android.provider.MediaStore.Audio.Genres;\r
 import android.provider.MediaStore.Audio.Playlists;\r
 import android.provider.MediaStore.MediaColumns;\r
-import android.support.v4.app.Fragment;\r
 import android.support.v4.app.LoaderManager.LoaderCallbacks;\r
 import android.support.v4.content.CursorLoader;\r
 import android.support.v4.content.Loader;\r
@@ -42,6 +41,7 @@ import com.andrew.apollo.adapters.TrackAdapter;
 import com.andrew.apollo.service.ApolloService;\r
 import com.andrew.apollo.utils.ApolloUtils;\r
 import com.andrew.apollo.utils.MusicUtils;\r
+import com.andrew.apollo.utils.RefreshableFragment;\r
 \r
 import static com.andrew.apollo.Constants.EXTERNAL;\r
 import static com.andrew.apollo.Constants.INTENT_ADD_TO_PLAYLIST;\r
@@ -53,7 +53,7 @@ import static com.andrew.apollo.Constants.PLAYLIST_QUEUE;
 /**\r
  * @author Andrew Neal\r
  */\r
-public class TracksFragment extends Fragment implements LoaderCallbacks<Cursor>,\r
+public class TracksFragment extends RefreshableFragment implements LoaderCallbacks<Cursor>,\r
         OnItemClickListener {\r
 \r
     // Adapter\r
@@ -116,6 +116,14 @@ public class TracksFragment extends Fragment implements LoaderCallbacks<Cursor>,
     }\r
 \r
     @Override\r
+    public void refresh() {\r
+        // The data need to be refreshed\r
+        if( mListView != null ) {\r
+            getLoaderManager().restartLoader(0, null, this);\r
+        }\r
+    }\r
+\r
+    @Override\r
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\r
         View root = inflater.inflate(R.layout.listview, container, false);\r
         mListView = (ListView)root.findViewById(android.R.id.list);\r
@@ -269,6 +277,7 @@ public class TracksFragment extends Fragment implements LoaderCallbacks<Cursor>,
             mAlbumIndex = data.getColumnIndexOrThrow(AudioColumns.ALBUM);\r
         }\r
         mTrackAdapter.changeCursor(data);\r
+        mListView.invalidateViews();\r
         mCursor = data;\r
     }\r
 \r
index bbccfcd..b521ab3 100644 (file)
@@ -1130,10 +1130,16 @@ public class ApolloService extends Service {
                     where = null;\r
                     selectionArgs = null;\r
                 } else {\r
+                    // Remove schema for search in the database\r
+                    // Otherwise the file will not found\r
+                    String data = path;\r
+                    if( data.startsWith("file://") ){\r
+                        data = data.substring(7);\r
+                    }\r
                     uri = MediaStore.Audio.Media.getContentUriForPath(path);\r
                     where = MediaColumns.DATA + "=?";\r
                     selectionArgs = new String[] {\r
-                        path\r
+                        data\r
                     };\r
                 }\r
 \r
@@ -1178,6 +1184,48 @@ public class ApolloService extends Service {
     }\r
 \r
     /**\r
+     * Method that query the media database for search a path an translate\r
+     * to the internal media id\r
+     *\r
+     * @param path The path to search\r
+     * @return long The id of the resource, or -1 if not found\r
+     */\r
+    public long getIdFromPath(String path) {\r
+        try {\r
+            // Remove schema for search in the database\r
+            // Otherwise the file will not found\r
+            String data = path;\r
+            if( data.startsWith("file://") ){\r
+                data = data.substring(7);\r
+            }\r
+            ContentResolver resolver = getContentResolver();\r
+            String where = MediaColumns.DATA + "=?";\r
+            String selectionArgs[] = new String[] {\r
+                data\r
+            };\r
+            Cursor cursor =\r
+                    resolver.query(\r
+                            MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,\r
+                            mCursorCols, where, selectionArgs, null);\r
+            try {\r
+                if (cursor == null || cursor.getCount() == 0) {\r
+                    return -1;\r
+                }\r
+                cursor.moveToNext();\r
+                return cursor.getLong(IDCOLIDX);\r
+            } finally {\r
+                try {\r
+                    if( cursor != null )\r
+                        cursor.close();\r
+                } catch (Exception ex) {\r
+                }\r
+            }\r
+        } catch (UnsupportedOperationException ex) {\r
+        }\r
+        return -1;\r
+    }\r
+\r
+    /**\r
      * Starts playback of a previously opened file.\r
      */\r
     public void play() {\r
@@ -1801,7 +1849,7 @@ public class ApolloService extends Service {
     public String getArtistName() {\r
         synchronized (this) {\r
             if (mCursor == null) {\r
-                return null;\r
+                return getString(R.string.unknown);\r
             }\r
             return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.ARTIST));\r
         }\r
@@ -1819,7 +1867,7 @@ public class ApolloService extends Service {
     public String getAlbumName() {\r
         synchronized (this) {\r
             if (mCursor == null) {\r
-                return null;\r
+                return getString(R.string.unknown);\r
             }\r
             return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.ALBUM));\r
         }\r
@@ -1837,7 +1885,7 @@ public class ApolloService extends Service {
     public String getTrackName() {\r
         synchronized (this) {\r
             if (mCursor == null) {\r
-                return null;\r
+                return getString(R.string.unknown);\r
             }\r
             return mCursor.getString(mCursor.getColumnIndexOrThrow(MediaColumns.TITLE));\r
         }\r
@@ -2117,6 +2165,11 @@ public class ApolloService extends Service {
         }\r
 \r
         @Override\r
+        public long getIdFromPath(String path) {\r
+            return mService.get().getIdFromPath(path);\r
+        }\r
+\r
+        @Override\r
         public int getQueuePosition() {\r
             return mService.get().getQueuePosition();\r
         }\r
index 3d899f0..13f91fa 100644 (file)
@@ -920,6 +920,21 @@ public class MusicUtils {
     }\r
 \r
     /**\r
+     * Method that removes all tracks from the current queue\r
+     */\r
+    public static void removeAllTracks() {\r
+        try {\r
+            if (mService == null) {\r
+                long[] current = MusicUtils.getQueue();\r
+                if (current != null) {\r
+                    mService.removeTracks(0, current.length-1);\r
+                }\r
+            }\r
+        } catch (RemoteException e) {\r
+        }\r
+    }\r
+\r
+    /**\r
      * @param id\r
      * @return removes track from a playlist\r
      */\r
diff --git a/src/com/andrew/apollo/utils/RefreshableFragment.java b/src/com/andrew/apollo/utils/RefreshableFragment.java
new file mode 100644 (file)
index 0000000..2d2249f
--- /dev/null
@@ -0,0 +1,31 @@
+/*\r
+ * Copyright (C) 2012 The CyanogenMod Project\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.andrew.apollo.utils;\r
+\r
+\r
+import android.support.v4.app.Fragment;\r
+\r
+/**\r
+ * An abstract class that defines a {@link Fragment} like refreshable\r
+ */\r
+public abstract class RefreshableFragment extends Fragment {\r
+\r
+    /**\r
+     * Method invoked when the fragment need to be refreshed\r
+     */\r
+    public abstract void refresh();\r
+}\r