/** * Copyright 2009 Marc Stogaitis and Mimi Sun * * 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 org.gmote.client.android; import java.io.File; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; import org.gmote.common.FileInfo; import org.gmote.common.MimeTypeResolver; import org.gmote.common.FileInfo.FileType; import org.gmote.common.Protocol.Command; import org.gmote.common.media.MediaMetaInfo; import org.gmote.common.packet.AbstractPacket; import org.gmote.common.packet.ListReplyPacket; import org.gmote.common.packet.MediaInfoPacket; import org.gmote.common.packet.MediaInfoReqPacket; import org.gmote.common.packet.RunFileReqPacket; import org.gmote.common.packet.SimplePacket; import android.app.Activity; import android.app.Dialog; import android.content.ActivityNotFoundException; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; /** * Controller logic for the remote control * * @author Mimi * */ public class ButtonControl extends Activity implements BaseActivity { private static final String DEBUG_TAG = "Gmote"; private static Bitmap mBitmap = null; private static GmoteMediaPlayer mediaPlayer = null; private static boolean inMediaPlayerMode = false; private static FileInfo fileInfo; private static ActivityUtil mUtil = null; private static int lastSeenDuration = 0; private static int lastSeenPercentage = 0; private static MediaMetaInfo mediaMetaInfo = null; private LocalMediaPlayerListener localMediaPlayerListener = new LocalMediaPlayerListener(); private View mContentView = null; private View mMediaInfoView = null; private TextView mMediaTitleView = null; private TextView mMediaArtistView = null; private ImageView mMediaImageView = null; /** Called when the activity is first created. */ @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); Log.i(DEBUG_TAG, "ButtonControl: onCreate()"); mUtil = new ActivityUtil(); mUtil.onCreate(icicle, this); Intent intent = getIntent(); fileInfo = (FileInfo) intent .getSerializableExtra(getString(R.string.file_type)); if (fileInfo != null) { boolean inSyncMode = intent.getBooleanExtra(getString(R.string.gmote_stream_mode), false); if (inSyncMode) { ListReplyPacket reply = (ListReplyPacket) intent.getSerializableExtra(getString(R.string.gmote_stream_playlist)); FileInfo[] playList = reply.getFiles(); startGmoteSyncMode(fileInfo, playList); } else { if (inMediaPlayerMode) { if (mediaPlayer != null) { mediaPlayer.handleCommand(Command.STOP); } } inMediaPlayerMode = false; mUtil.send(new RunFileReqPacket(fileInfo)); } } /* * uncomment this section to support different views based on file type * FileType type = null; if (path != null) type = * FileType.valueOf(typeName); * * get view based on file type if (type==FileType.VIDEO || * type==FileType.MUSIC) { } else { attachGenericView(); } */ } private void startGmoteSyncMode(FileInfo fileInfo, FileInfo[] playList) { Remote remoteInstance = Remote.getInstance(); String serverUrl = remoteInstance.getServerIp() + ":" + remoteInstance.getServerPort() + "/"; if (!serverUrl.startsWith("http://")) { serverUrl = "http://" + serverUrl; } if (fileInfo.getFileType() == FileType.MUSIC) { startGmoteAudioPlayer(fileInfo, playList, serverUrl); } else if (fileInfo.getFileType() == FileType.IMAGE) { System.out.println("### images: " + playList.length); ArrayList<String> images = new ArrayList<String>(); int startingImageIndex = 0; for (FileInfo file : playList) { if (file.equals(fileInfo)) { startingImageIndex = images.size(); } if (file.getFileType() == FileType.IMAGE) { images.add(createUrlFromFilename(file, serverUrl, true)); } } Intent intent = new Intent(this, ImageBrowser.class); intent.putStringArrayListExtra(getString(R.string.gmote_stream_playlist), images); intent.putExtra(getString(R.string.file_type), startingImageIndex); startActivity(intent); finish(); } else { startExternalActivity(fileInfo, serverUrl); } } private String createUrlFromFilename(FileInfo fileInfo, String serverUrl, boolean encodeForGmoteHttpServer) { String fileName = "files/" + fileInfo.getAbsolutePath(); String encodedFileName; try { // TODO(mstogaitis): Fix this so that we only use one encoding. if (!encodeForGmoteHttpServer) { encodedFileName = Uri.encode(fileName); } else { encodedFileName = URLEncoder.encode(fileName, "UTF-8"); } } catch (UnsupportedEncodingException e) { Log.e(DEBUG_TAG, e.getMessage(), e); encodedFileName = "UnsupportedEncodingException"; } return serverUrl + encodedFileName; } private void startGmoteAudioPlayer(FileInfo fileInfo, FileInfo[] playList, String serverUrl) { inMediaPlayerMode = true; if (mediaPlayer == null) { mediaPlayer = new GmoteMediaPlayer(localMediaPlayerListener); } else { mediaPlayer.setMediaPlayerListener(localMediaPlayerListener); } List<String> songs = new ArrayList<String>(); int startingSongIndex = 0; for (FileInfo file : playList) { if (file.equals(fileInfo)) { startingSongIndex = songs.size(); } if (file.getFileType() == FileType.MUSIC) { songs.add(createUrlFromFilename(file, serverUrl, true)); } } mediaPlayer.playSongs(songs, startingSongIndex); } private void startExternalActivity(FileInfo fileInfo, String serverUrl) { Intent intent = new Intent(Intent.ACTION_VIEW); String contentType = MimeTypeResolver.findMimeType(fileInfo.getAbsolutePath()); boolean unknownContentType = contentType.equals(MimeTypeResolver.UNKNOWN_MIME_TYPE); if (unknownContentType) { if (fileInfo.getFileType() == FileType.MUSIC) { contentType = "audio/unknown"; unknownContentType = false; } else if (fileInfo.getFileType() == FileType.VIDEO) { contentType = "video/unknown"; unknownContentType = false; } } String sessionId = Remote.getInstance().getSessionId(); if (sessionId == null) { Log.i(DEBUG_TAG, "Null session id when trying to start an external activity"); ActivityUtil.showMessageBox(this, "Error", "Encountered a null session id. Please re-connect to the server by clicking 'menu', 'Gmote Stream' and try again"); return; } String url = createUrlFromFilename(fileInfo, serverUrl, !unknownContentType) + "?sessionId=" + sessionId; if (contentType.toLowerCase().startsWith("audio/") || contentType.toLowerCase().startsWith("video/")) { intent.setDataAndType(Uri.parse(url.toString()), contentType); } else { intent.setData(Uri.parse(url.toString())); } Log.i(DEBUG_TAG, "Uri is: " + url.toString()); try { startActivity(intent); } catch (ActivityNotFoundException e) { Log.e(DEBUG_TAG, e.getMessage(), e); Toast.makeText(ButtonControl.this,"Gmote is unable to find an android application to play this file type: " + e.getMessage(), Toast.LENGTH_LONG).show(); } } @Override public void onStart() { super.onStart(); Log.i(DEBUG_TAG, "ButtonControl: onStart()"); mUtil.onStart(this); attachMediaView(); } @Override public void onResume() { super.onResume(); Log.i(DEBUG_TAG, "ButtonControl: onResume()"); mUtil.onResume(); if (inMediaPlayerMode && mediaPlayer != null) { mediaPlayer.setMediaPlayerListener(localMediaPlayerListener); if (localMediaPlayerListener != null) { localMediaPlayerListener.updateData(); } } } @Override public void onPause() { super.onPause(); Log.i(DEBUG_TAG, "ButtonControl: onPause()"); mUtil.onPause(); } @Override public void onStop() { super.onStop(); Log.i(DEBUG_TAG, "ButtonControl: onStop()"); mUtil.onStop(); } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); mUtil.onCreateOptionsMenu(menu); menu.removeItem(R.id.menui_remote_control); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { super.onOptionsItemSelected(item); return mUtil.onOptionsItemSelected(item); } @Override protected Dialog onCreateDialog(int id) { return mUtil.onCreateDialog(id); } public void handleReceivedPacket(AbstractPacket reply) { System.out.println("ButtonControl got packet"); if (reply.getCommand() == Command.MEDIA_INFO) { updateMediaInfo(((MediaInfoPacket) reply).getMedia()); } } private void attachMediaView() { int layoutId = R.layout.media_view; int viewId = R.id.media_view; setContentView(layoutId); mContentView = findViewById(viewId); ArrayList<View> views = mContentView.getTouchables(); for (View v : views) { if (v == null || v.getId() == R.id.browse) continue; v.setOnClickListener(mListener); if (v.getId() == R.id.fast_forward || v.getId() == R.id.rewind) { v.setLongClickable(true); v.setOnLongClickListener(mLongListener); } } View browse = findViewById(R.id.browse); browse.setOnClickListener(mBrowseListener); LayoutInflater factory = LayoutInflater.from(this); mMediaInfoView = factory.inflate(R.layout.media_info, null); ((ViewGroup) mContentView).addView(mMediaInfoView); initMediaInfo(); } void initMediaInfo() { mMediaTitleView = (TextView) (mMediaInfoView .findViewById(R.id.media_info_title)); mMediaArtistView = (TextView) (mMediaInfoView .findViewById(R.id.media_info_artist)); mMediaImageView = (ImageView) (mMediaInfoView .findViewById(R.id.media_info_image)); } synchronized void updateMediaInfo(MediaMetaInfo mediaMeta) { if (mediaMeta == null || mediaMeta.getTitle() == null && mediaMeta.getArtist() == null && mediaMeta.getImage() == null) { mMediaInfoView.setVisibility(ActivityUtil.VIEW_GONE); mContentView.setBackgroundDrawable(null); mMediaImageView.setImageBitmap(null); mMediaTitleView.setText(""); mMediaArtistView.setText(""); } else { mMediaInfoView.setVisibility(ActivityUtil.VIEW_VISIBLE); if (mediaMeta.getTitle() != null) { mMediaTitleView.setText(mediaMeta.getTitle()); } if (mediaMeta.getArtist() != null) { mMediaArtistView.setText(mediaMeta.getArtist()); } try { byte[] image = mediaMeta.getImage(); if (image != null) { int length = image.length; if (length > 10) { if (mBitmap != null) { mBitmap.recycle(); } Log.e(ActivityUtil.DEBUG_TAG, "ButtonControl# changing image"); mBitmap = BitmapFactory.decodeByteArray(image, 0, length); mMediaImageView.setImageBitmap(mBitmap); if (mediaMeta.isShowImageOnBackground()) { mContentView.setBackgroundDrawable(new BitmapDrawable(mBitmap)); } } else { Log.e(ActivityUtil.DEBUG_TAG, "ButtonControl# same album"); } } else { Log.w(ActivityUtil.DEBUG_TAG, "ButtonControl# null image"); if (mBitmap != null && !mediaMeta.isImageSameAsPrevious()) { mMediaImageView.setImageResource(R.drawable.audio); mContentView.setBackgroundDrawable(null); mBitmap.recycle(); mBitmap = null; } } } catch (Exception e) { Log.e(DEBUG_TAG, e.getMessage(), e); } } mediaMeta = null; } private OnClickListener mBrowseListener = new OnClickListener() { public void onClick(View v) { Log.d(ActivityUtil.DEBUG_TAG, "ButtonControl# clicked Browse"); Intent intent = new Intent(ButtonControl.this, Browse.class); startActivity(intent); } }; View.OnClickListener mListener = new OnClickListener() { public void onClick(View v) { String command = (String) v.getTag(); Log.d(ActivityUtil.DEBUG_TAG, "ButtonControl# clicked" + command); if (inMediaPlayerMode) { mediaPlayer.handleCommand(Command.valueOf(command)); } else { mUtil.send(new SimplePacket(Command.valueOf(command))); } } }; View.OnLongClickListener mLongListener = new OnLongClickListener() { public boolean onLongClick(View v) { String commandName = (String) v.getTag(); Log.d(ActivityUtil.DEBUG_TAG, "ButtonControl# long-clicked" + commandName); if (inMediaPlayerMode) { mediaPlayer.handleCommand(Command.valueOf(commandName + "_LONG")); } else { mUtil.send(new SimplePacket(Command.valueOf(commandName + "_LONG"))); } return true; } }; private class LocalMediaPlayerListener extends Handler { @Override public synchronized void handleMessage(Message msg) { if (msg.what == GmoteMediaPlayer.BUFFERING_UPDATE) { int percent = (Integer)msg.obj; lastSeenPercentage = percent; displayBufferAndTime(lastSeenPercentage, lastSeenDuration); } else if (msg.what == GmoteMediaPlayer.MEDIA_INFO_UPDATE) { MediaMetaInfo meta = (MediaMetaInfo)msg.obj; String songNameAndPath = meta.getTitle(); if (songNameAndPath == null) { return; } mUtil.send(new MediaInfoReqPacket(songNameAndPath, mBitmap == null)); meta.setTitle(new File(songNameAndPath).getName()); mediaMetaInfo = meta; displayTitle(mediaMetaInfo.getTitle()); } else if (msg.what == GmoteMediaPlayer.MEDIA_DURATION_UPDATE) { int duration = (Integer)msg.obj; lastSeenDuration = duration; displayBufferAndTime(lastSeenPercentage, lastSeenDuration); } else if (msg.what == GmoteMediaPlayer.MEDIA_PLAYER_ERROR) { mUtil.cancelDialog(); String message = (String)msg.obj; mediaMetaInfo = new MediaMetaInfo("",message, null,null,false); Toast.makeText(ButtonControl.this,"An error occurred during playback. This can happen when your phone experiences connection issues. " + message, Toast.LENGTH_LONG).show(); updateMediaInfo(mediaMetaInfo); lastSeenDuration = 0; lastSeenPercentage = 0; } else if (msg.what == GmoteMediaPlayer.SESSION_ERROR) { String message = (String)msg.obj; ActivityUtil.showMessageBox(ButtonControl.this, "PlayOnPhone(beta) Error" , message); lastSeenDuration = 0; lastSeenPercentage = 0; } else if (msg.what == GmoteMediaPlayer.PREPARING_MEDIA) { lastSeenDuration = 0; lastSeenPercentage = 0; displayTitle(""); mMediaArtistView.setText("loading..."); displayMediaArt(); mMediaInfoView.setVisibility(ActivityUtil.VIEW_VISIBLE); } } private void displayMediaArt() { if (mBitmap == null) { mMediaImageView.setImageResource(R.drawable.audio); } else { mMediaImageView.setImageBitmap(mBitmap); mContentView.setBackgroundDrawable(new BitmapDrawable(mBitmap)); } } private void displayTitle(String title) { mMediaTitleView.setText(title); mMediaInfoView.setVisibility(ActivityUtil.VIEW_VISIBLE); } private void displayBufferAndTime(int percent, int duration) { mMediaArtistView.setText((duration == 0 ? "" : GmoteMediaPlayer.formatTime(duration) + " - ") + "Buffering: " + percent + "%"); mMediaInfoView.setVisibility(ActivityUtil.VIEW_VISIBLE); } /** * Displays the latest data. Typically used when onResume() is called. */ public synchronized void updateData() { if (mediaMetaInfo != null && mediaMetaInfo.getAlbum() == null && mediaMetaInfo.getArtist() == null && mediaMetaInfo.getImage() == null) { displayTitle(mediaMetaInfo.getTitle()); } else { //updateMediaInfo(metaInfo); } displayMediaArt(); if (lastSeenDuration != 0 || lastSeenPercentage != 0) { displayBufferAndTime(lastSeenPercentage, lastSeenDuration); } } } }