/* * Copyright (c) 2013 Allogy Interactive. * * 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.allogy.app.media; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import android.app.Activity; import android.content.ContentValues; import android.content.Intent; import android.content.res.Configuration; import android.database.Cursor; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.util.Log; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.View; import android.view.View.OnClickListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemLongClickListener; import android.widget.EditText; import android.widget.ImageButton; import android.widget.ListView; import android.widget.PopupWindow; import android.widget.TextView; import android.widget.Toast; import com.allogy.app.HomeActivity; import com.allogy.app.R; import com.allogy.app.adapter.NotesCursorAdapter; import com.allogy.app.adapter.NotesCursorAdapter.NoteView; import com.allogy.app.provider.Academic; import com.allogy.app.provider.Notes; import com.allogy.app.ui.ActionItem; import com.allogy.app.ui.AllogyVideoView; import com.allogy.app.ui.AnnotatedProgressBar; import com.allogy.app.ui.QuickAction; import com.allogy.app.util.Util; /** * <p> * This class instantiates the AllogyVideoView class and creates the mPlayer * activity. Basic video functionality is provided. * </p> * <p> * TODO: See if we can merge some of the code from the AudioPlayerActivity and * the VideoPlayerActivity. (i.e. make a base activity for common functions and * events.) * </p> * * @author Jay Morrow * @author Diego Nunez * **/ public class VideoPlayerActivity extends Activity implements OnCompletionListener, MediaPlayer.OnPreparedListener, SurfaceHolder.Callback, PlaybackTimer { // / // / CONSTANTS // / private static final String LOG_TAG = VideoPlayerActivity.class.getName(); public static final String INTENT_EXTRA_LESSONFILEID = "videoplayer.extra.lessonfileid"; public static final String INTENT_EXTRA_CURRENTPLAYBACKTIME = "videoplayer.extra.currentplaybacktime"; // / // / PROPERTIES // / private int currentPlaybackTime = Util.OUT_OF_BOUNDS; private MediaPlayer mMediaPlayer; private AllogyVideoView mVideoPlayer; private SurfaceHolder mHolder; private boolean isCompleted = false; private AnnotatedProgressBar mProgressBar = null; private VideoItem mCurrentVideo; private Handler mHandler = new Handler(); private QuickAction mQuickAction = null; private TextView mCurrentAnnote = null; private View mEditingAnnotation = null; private boolean isFirstTimeClick = true; /** * <p> * The map is used to store local copies of the notes, as such we do not * have to continuously check to database in order to figure out which note * to display on audio play back. * </p> */ private Map<Integer, String> mAnnotationProgressNoteMap = new HashMap<Integer, String>(); // / // / ACTIVITY METHODS // / @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_videoplayer); // Retrieve or create necessary views. mVideoPlayer = (AllogyVideoView) findViewById(R.id.videoplayer_allogyvideoview); mVideoPlayer.addTapListener(onTap); mHolder = mVideoPlayer.getHolder(); mHolder.addCallback(this); mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); mProgressBar = (AnnotatedProgressBar) this .findViewById(R.id.videoplayer_progress); mProgressBar.SetOnSeekListener(new OnSeekListener() { @Override public void onSeeking() { } @Override public void onSeekStarted(int progress) { } @Override public void onSeekFinished(int progress) { if (mMediaPlayer != null) { mMediaPlayer.seekTo(progress); } } }); if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { // Initialize quick action popup. mQuickAction = new QuickAction( this.findViewById(R.id.videoplayer_lv_notes)); ActionItem ai = new ActionItem(); ai.setTitle("Delete"); ai.setOnClickListener(mActionItemClick); mQuickAction.setAnimStyle(QuickAction.ANIM_AUTO); mQuickAction.addActionItem(ai); mQuickAction.setOnDismissListener(mActionDismissed); // Set listeners to the notes list view. ListView lv = (ListView) this .findViewById(R.id.videoplayer_lv_notes); lv.setOnItemClickListener(mAnnClickListener); lv.setOnItemLongClickListener(mAnnItemLongClick); // Get the TextView to display during play back. mCurrentAnnote = (TextView) this .findViewById(R.id.videoplayer_tv_note); } // Register the current video to be played. Intent i = this.getIntent(); int lessonfile = i.getIntExtra( VideoPlayerActivity.INTENT_EXTRA_LESSONFILEID, Util.OUT_OF_BOUNDS); RegisterCurrentVideo(lessonfile); currentPlaybackTime = RegisterPlaybackTime(lessonfile, i.getIntExtra( VideoPlayerActivity.INTENT_EXTRA_CURRENTPLAYBACKTIME, Util.OUT_OF_BOUNDS)); } @Override protected void onResume() { super.onResume(); } @Override protected void onPause() { super.onPause(); mVideoPlayer.removeTapListener(onTap); StopPlaybackTimer(); if (mMediaPlayer != null) { int temp = mMediaPlayer.getCurrentPosition(); BookmarkPlayback(temp); // this.getIntent().putExtra( // VideoPlayerActivity.INTENT_EXTRA_CURRENTPLAYBACKTIME, temp); mMediaPlayer.release(); mMediaPlayer = null; } } @Override protected void onDestroy() { super.onDestroy(); mVideoPlayer.removeTapListener(onTap); StopPlaybackTimer(); if (mMediaPlayer != null) { int temp = mMediaPlayer.getCurrentPosition(); BookmarkPlayback(temp); this.getIntent().putExtra( VideoPlayerActivity.INTENT_EXTRA_CURRENTPLAYBACKTIME, temp); mMediaPlayer.release(); mMediaPlayer = null; } } // / // / METHODS // / /** * Retrieves the saved playback progress either from the database, or from * the <b>Intent</b>. * * @param time * The value saved in INTENT_EXTRA_CURRENTPLAYBACKTIME field, if * any. * @param lessonfile * The primary key of a file saved in the database that belongs * to a lesson. * @return The appropriate progress for which to start playback. */ private int RegisterPlaybackTime(int lessonfile, int time) { int result = 0; if (time > Util.OUT_OF_BOUNDS) { result = time; } else if (lessonfile > Util.OUT_OF_BOUNDS) { Cursor cursor = this.getContentResolver().query( Academic.Progress.CONTENT_URI, new String[] { Academic.Progress.PROGRESS }, String.format("%s = ? AND %s = ?", Academic.Progress.CONTENT_ID, Academic.Progress.CONTENT_TYPE), new String[] { Integer.toString(lessonfile), Integer.toString(Academic.CONTENT_TYPE_VIDEO) }, null); if (cursor.moveToFirst()) { result = cursor.getInt(cursor .getColumnIndexOrThrow(Academic.Progress.PROGRESS)); } cursor.close(); } return result; } /** * Attempts to retrieve data with the provided lesson file id. * * @param lessonfileid * The primary key of a file saved in the database that belongs * to a lesson. */ private void RegisterCurrentVideo(int lessonfileid) { if (lessonfileid != Util.OUT_OF_BOUNDS) { Cursor cursor = this.getContentResolver().query( Academic.LessonFiles.CONTENT_URI, new String[] { Academic.LessonFiles.URI }, String.format("%s = ?", Academic.LessonFiles._ID), new String[] { Integer.toString(lessonfileid) }, null); if (cursor.moveToFirst()) { // Get the path of the video on the // sdcard. mCurrentVideo = new VideoItem( lessonfileid, Environment.getExternalStorageDirectory() + "/Allogy/Decrypted/" + cursor.getString( cursor.getColumnIndexOrThrow(Academic.LessonFiles.URI)) .replace(".mp4", "").trim()); // Initialize the notes for the video. PrepareProgressAnnotations(lessonfileid); } else { mCurrentVideo = null; } cursor.close(); } } /** * Adds annotations to the <b>AnnotatedProgressBar</b> of all the * <b>Notes</b> retrieved for the file. * * @param id * The primary key of a file saved in the database that belongs * to a lesson. */ private void PrepareProgressAnnotations(int id) { if (!mAnnotationProgressNoteMap.isEmpty()) { mAnnotationProgressNoteMap.clear(); } if (mProgressBar.HasAnnotations()) { mProgressBar.ClearAnnotations(); } Cursor cursor = Notes.GetNotes(this, id, Notes.Note.TYPE_VIDEO); if (cursor.moveToFirst()) { do { int progress = cursor.getInt(cursor .getColumnIndexOrThrow(Notes.Note.TIME)); mProgressBar.AddAnnotation(progress); mAnnotationProgressNoteMap.put(progress, cursor .getString(cursor .getColumnIndexOrThrow(Notes.Note.BODY))); } while (cursor.moveToNext()); } cursor.close(); } /** * Re-populates the <b>Notes</b> <b>ListView</b> with the * <b>NotesCursorAdapter</b>. */ private void UpdateNotesList() { ListView lv = (ListView) this.findViewById(R.id.videoplayer_lv_notes); if (lv.getVisibility() == View.VISIBLE) { lv.setAdapter(new NotesCursorAdapter(this, mCurrentVideo.mID, Notes.Note.TYPE_VIDEO)); } } /** * Saves the current playback progress of the current video to the database. * * @param progress * The current playback progress in milliseconds. */ private void BookmarkPlayback(int progress) { int lessonfile = this.getIntent().getIntExtra( VideoPlayerActivity.INTENT_EXTRA_LESSONFILEID, Util.OUT_OF_BOUNDS); if (lessonfile > Util.OUT_OF_BOUNDS) { Cursor cursor = this.getContentResolver().query( Academic.Progress.CONTENT_URI, new String[] { Academic.Progress._ID }, String.format("%s = ? AND %s = ?", Academic.Progress.CONTENT_ID, Academic.Progress.CONTENT_TYPE), new String[] { Integer.toString(lessonfile), Integer.toString(Academic.CONTENT_TYPE_VIDEO) }, null); ContentValues values = new ContentValues(); values.put(Academic.Progress.CONTENT_ID, lessonfile); values.put(Academic.Progress.CONTENT_TYPE, Academic.CONTENT_TYPE_VIDEO); values.put(Academic.Progress.PROGRESS, progress); if (cursor.moveToFirst()) { // If a progress already exists, then update it. this.getContentResolver() .update(Academic.Progress.CONTENT_URI, values, String.format("%s = ?", Academic.Progress._ID), new String[] { Integer.toString(cursor.getInt(cursor .getColumnIndexOrThrow(Academic.Progress._ID))) }); } else { // No progress exists, so create it. this.getContentResolver().insert(Academic.Progress.CONTENT_URI, values); } cursor.close(); } } /** * Retrieves the correct Note to display, if any, for the given timestamp. * * @param timestamp * The current progress of playback. */ private void RegisterAnnotationFromTimeStamp(int timestamp) { ArrayList<Integer> keys = new ArrayList<Integer>( mAnnotationProgressNoteMap.keySet()); Collections.sort(keys); int key = 0; for (int i = 0, j = 1, len = keys.size(); j <= len; i++, j++) { key = keys.get(i); if (j == len) { if (timestamp >= keys.get(i)) { mCurrentAnnote.setText(mAnnotationProgressNoteMap.get(key)); } } else { if (timestamp >= keys.get(i) && timestamp < keys.get(j)) { mCurrentAnnote.setText(mAnnotationProgressNoteMap.get(key)); } } } } /** * Attemps to start playback of the current video. * * @param url * The path of the file on the SD Card. */ private void playVideo(String url) { try { File file = new File(url); if (!file.exists() || !file.isFile()) { mCurrentVideo = null; return; } if (mMediaPlayer == null) { mMediaPlayer = new MediaPlayer(); mMediaPlayer.setScreenOnWhilePlaying(true); } else { mMediaPlayer.stop(); mMediaPlayer.reset(); } isCompleted = false; mMediaPlayer.setDataSource(url); mMediaPlayer.setDisplay(mHolder); mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mMediaPlayer.setOnPreparedListener(this); mMediaPlayer.prepareAsync(); mMediaPlayer.setOnCompletionListener(this); } catch (IOException ioe) { mCurrentVideo = null; } catch (IllegalStateException ise) { mMediaPlayer.reset(); } } /** * Sets the visibility of all of the controls that appear over the video * surface. * * @param reset * If true then we make all of the controls invisible, otherwise * make them visible depending on the visibility of the playback * button. */ private void DisplayVideoControls(boolean reset) { if (reset) { this.findViewById(R.id.videoplayer_ibtn_playback).setVisibility( View.INVISIBLE); this.findViewById(R.id.videoplayer_ibtn_scanback).setVisibility( View.INVISIBLE); this.findViewById(R.id.videoplayer_ibtn_scanforward).setVisibility( View.INVISIBLE); this.findViewById(R.id.videoplayer_progress).setVisibility( View.INVISIBLE); } else { ImageButton btn = (ImageButton) this .findViewById(R.id.videoplayer_ibtn_playback); int temp = btn.getVisibility(); btn.setVisibility(temp == View.VISIBLE ? View.INVISIBLE : View.VISIBLE); this.findViewById(R.id.videoplayer_ibtn_scanback).setVisibility( temp == View.VISIBLE ? View.INVISIBLE : View.VISIBLE); this.findViewById(R.id.videoplayer_ibtn_scanforward).setVisibility( temp == View.VISIBLE ? View.INVISIBLE : View.VISIBLE); this.findViewById(R.id.videoplayer_progress).setVisibility( temp == View.VISIBLE ? View.INVISIBLE : View.VISIBLE); } } /** * Performs the necessary logic for hiding the Note creation functionality, * as well as providing the ability to display the <b>Notes</b> * <b>ListView</b>. * * @param showNote * If true then the <b>TextView</b> displaying the current Note * is displayed. The <b>ListView</b> displaying all of the * <b>Notes</b> is displayed otherwise. */ private void CancelAddNote(boolean showNote) { this.findViewById(R.id.videoplayer_tv_note).setVisibility( showNote ? View.VISIBLE : View.INVISIBLE); this.findViewById(R.id.videoplayer_lv_notes).setVisibility( !showNote ? View.VISIBLE : View.INVISIBLE); EditText et = (EditText) this.findViewById(R.id.videoplayer_et_note); et.setText(""); et.setVisibility(View.INVISIBLE); this.findViewById(R.id.videoplayer_btn_cancelnote).setVisibility( View.INVISIBLE); this.findViewById(R.id.videoplayer_btn_savenote).setVisibility( View.INVISIBLE); } // / // / THREADS // / private Timer mPlaybackTimer = null; @Override public void StartPlaybackTimer() { StopPlaybackTimer(); mPlaybackTimer = new Timer(); mPlaybackTimer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { UpdatePlaybackProgress(); } }, 0, TIMER_UPDATE_INTERVAL); } @Override public void UpdatePlaybackProgress() { mHandler.post(mUpdateRunnable); } @Override public void StopPlaybackTimer() { if (null != mPlaybackTimer) { mPlaybackTimer.cancel(); mPlaybackTimer = null; } } /** * Updates the <b>AnnotatedProgressBar</b> on the <b>Activity</b> with the * current progress of the playback. It also calls the * ResiterAnnotationFromTimeStamp method to retrieves the appropriate Note * to display. */ private Runnable mUpdateRunnable = new Runnable() { @Override public void run() { int progress = mMediaPlayer.getCurrentPosition(); mProgressBar.SetProgress(progress); if(mMediaPlayer.isPlaying()) { Log.i("VideoPlayerActivity", "progess: " + progress); } if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { // TODO: Get current annotation from playback progress. RegisterAnnotationFromTimeStamp(progress); } } }; // / // / EVENT LISTENERS // / @Override public void onPrepared(MediaPlayer MediaPlayer) { int width = mMediaPlayer.getVideoWidth(); int height = mMediaPlayer.getVideoHeight(); if (width != 0 && height != 0) { mHolder.setFixedSize(width, height); mProgressBar.SetMaxProgress(mMediaPlayer.getDuration()); if (currentPlaybackTime != Util.OUT_OF_BOUNDS) { mMediaPlayer.seekTo(currentPlaybackTime); mProgressBar.SetProgress(currentPlaybackTime); } else { mProgressBar.SetProgress(0); } StartPlaybackTimer(); mMediaPlayer.start(); } } /* * When the video is finished playing we make sure to reset the necessary * variables, stop the update timer for the playback, switch the pause image * to a play image, and set a flag that the playback has been completed. */ @Override public void onCompletion(MediaPlayer mp) { StopPlaybackTimer(); mProgressBar.SetProgress(mp.getDuration()); ((ImageButton) this.findViewById(R.id.videoplayer_ibtn_playback)) .setImageResource(R.drawable.play_button); isCompleted = true; currentPlaybackTime = 0; if(getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { mCurrentAnnote.setText(""); } } /* * When the activity loads and the surface which will hold the video is * created and ready to be used, we make sure we have a valid video and call * the playVideo method to try to play the video. If we could not get the * video to play, then a Toast (like a message box) is shown to the user. */ @Override public void surfaceCreated(SurfaceHolder surfaceholder) { if (null != mCurrentVideo) { File file = new File(mCurrentVideo.mUri); if (file.exists() && file.isFile()) { playVideo(mCurrentVideo.mUri); } else { mCurrentVideo = null; } } if (null == mCurrentVideo) { Toast.makeText(this, "Media could not be loaded!", Toast.LENGTH_LONG).show(); } } @Override public void surfaceChanged(SurfaceHolder surfaceholder, int i, int j, int k) { mHolder = surfaceholder; } @Override public void surfaceDestroyed(SurfaceHolder surfaceholder) { // Not used. } /* * When the user taps on the video, then we display hidden controls over the * video's surface. */ private AllogyVideoView.TapListener onTap = new AllogyVideoView.TapListener() { public void onTap(MotionEvent event) { VideoPlayerActivity.this.DisplayVideoControls(false); // This was a hack made to fix the problem Bug #455. // The problem was that the the controls were not showing up when a taps was performed // After experimenting, it was noticed that the controls were hiding behind the video // and if the touch happens at the place where "the play/pause" button should have // existed, the controls show up. So as a fix, I have added a code which performs // the click operation on the button. if(isFirstTimeClick) { ((ImageButton) VideoPlayerActivity.this.findViewById(R.id.videoplayer_ibtn_playback)) .performClick(); isFirstTimeClick = false; } } }; /** * Event listener for click event on the buttons of the * <b>VideoPlayerActivity</b> navigation header. * * @param view * The button element that was clicked. */ public void HeaderButtonsListener(View view) { // Each button has a unique id, so we can use a switch. switch (view.getId()) { case R.id.videoplayer_ibtn_home: Intent intent = new Intent(); intent.setClass(this, HomeActivity.class); this.startActivity(intent); this.finish(); break; case R.id.videoplayer_ibtn_search: // TODO: Call search functionality. break; default: break; } } /** * Event listener for click events on the buttons of the * <b>VideoPlayerActivity</b> that control all other functionality except * for the header. * * @param view * The button element that was clicked. */ public void ButtonsListener(View view) { // Do not try to access the other functionality if // we do not have a valid video. if (null == mCurrentVideo) { Toast.makeText(this, "Media could not be loaded!", Toast.LENGTH_LONG).show(); return; } // Each button has a unique id, so we can use a switch. switch (view.getId()) { case R.id.videoplayer_ibtn_add_notes: // When the add notes button is clicked, then we need to // display the hidden EditText and Cancel/Save buttons. this.findViewById(R.id.videoplayer_tv_note).setVisibility( View.INVISIBLE); this.findViewById(R.id.videoplayer_et_note).setVisibility( View.VISIBLE); this.findViewById(R.id.videoplayer_btn_cancelnote).setVisibility( View.VISIBLE); this.findViewById(R.id.videoplayer_btn_savenote).setVisibility( View.VISIBLE); // Stop the playback so the user can create the note. if (mMediaPlayer.isPlaying()) { mMediaPlayer.pause(); } break; case R.id.videoplayer_ibtn_list_notes: // Show a ListView of all the available notes for the video. ListView lv = (ListView) this .findViewById(R.id.videoplayer_lv_notes); if (lv.getVisibility() == View.VISIBLE) { CancelAddNote(true); } else { CancelAddNote(false); lv.setAdapter(new NotesCursorAdapter(this, mCurrentVideo.mID, Notes.Note.TYPE_VIDEO)); } break; case R.id.videoplayer_ibtn_scanback: if (mMediaPlayer != null) { int to = mMediaPlayer.getCurrentPosition() - 3000; Log.i("VideoPlayerActivity", "seeking back to: " + to); mMediaPlayer.seekTo(to); } break; case R.id.videoplayer_ibtn_playback: // Changes the image between play and pause, as well as the playback // state. if (null != mCurrentVideo && null != mMediaPlayer) { if (mMediaPlayer.isPlaying()) { ((ImageButton) view) .setImageResource(R.drawable.play_button); mMediaPlayer.pause(); } else { if (!isCompleted) { ((ImageButton) view) .setImageResource(R.drawable.pause_button); mMediaPlayer.start(); } else { playVideo(mCurrentVideo.mUri); } } } break; case R.id.videoplayer_ibtn_scanforward: if (mMediaPlayer != null) { int to = mMediaPlayer.getCurrentPosition() + 3000; Log.i("VideoPlayerActivity", "seeking forward to: " + to); mMediaPlayer.seekTo(to); } break; case R.id.videoplayer_btn_savenote: // NOTE: // R.id.videoplayer_btn_cancelnote // must come after this case. if (null == mEditingAnnotation) { String tempS = ((EditText) this .findViewById(R.id.videoplayer_et_note)).getText() .toString(); if (!Util.isNullOrEmpty(tempS)) { int progress = mMediaPlayer.getCurrentPosition(); // Add to the local copy. mProgressBar.AddAnnotation(progress); mAnnotationProgressNoteMap.put(progress, tempS); // Add to the database. ContentValues values = new ContentValues(); values.put(Notes.Note.CONTENT_ID, mCurrentVideo.mID); values.put(Notes.Note.TYPE, Notes.Note.TYPE_VIDEO); values.put(Notes.Note.BODY, tempS); values.put(Notes.Note.TIME, progress); VideoPlayerActivity.this.getContentResolver().insert( Notes.Note.CONTENT_URI, values); } else { Toast.makeText(this, "Note can not be empty!", Toast.LENGTH_SHORT).show(); break; } } else { NoteView nview = (NoteView) mEditingAnnotation.getTag(); String tempS = nview.body.getText().toString(); if (!Util.isNullOrEmpty(tempS)) { // Update the local copy. mAnnotationProgressNoteMap.put( (Integer) nview.time.getTag(), tempS); // Update the database. ContentValues values = new ContentValues(); values.put(Notes.Note.BODY, tempS); VideoPlayerActivity.this.getContentResolver().update( Notes.Note.CONTENT_URI, values, String.format("%s = ?", Notes.Note._ID), new String[] { Integer.toString(nview.id) }); } else { Toast.makeText(this, "Note can not be empty!", Toast.LENGTH_SHORT).show(); break; } } // we fall through to the logic for the cancel note button. case R.id.videoplayer_btn_cancelnote: CancelAddNote(true); if (!isCompleted) { mMediaPlayer.start(); } mEditingAnnotation = null; break; default: break; } } /** * Event listener for <b>ListView</b> item click event. We make the * currently clicked Note item switch to edit mode. */ private OnItemClickListener mAnnClickListener = new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (null == mEditingAnnotation) { view.findViewById(R.id.list_item_audioplayer_annotation_et_note) .setVisibility(View.GONE); view.findViewById( R.id.list_item_audioplayer_annotation_et_note_edit) .setVisibility(View.VISIBLE); mEditingAnnotation = view; VideoPlayerActivity.this.findViewById( R.id.videoplayer_btn_savenote).setVisibility( View.VISIBLE); VideoPlayerActivity.this.findViewById( R.id.videoplayer_btn_cancelnote).setVisibility( View.VISIBLE); } } }; /** * Event listener for <b>ListView</b> item long click event. We pop up the * <b>QuickAction</b> to allow a user more editing options. */ private OnItemLongClickListener mAnnItemLongClick = new OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { if (null == mEditingAnnotation) { mEditingAnnotation = view; mQuickAction.show(); } else { Toast.makeText(VideoPlayerActivity.this, "Pending edits must be finilized...", Toast.LENGTH_SHORT).show(); } return true; } }; /** * Event listener for <b>QuickAction</b> <b>ActionItem</b> events. We handle * the delete option of the <b>QuickAction</b>. */ private OnClickListener mActionItemClick = new OnClickListener() { @Override public void onClick(View view) { String title = ((TextView) view.findViewById(R.id.title)).getText() .toString(); if (null != mEditingAnnotation) { if (title == "Delete") { NoteView nview = (NoteView) mEditingAnnotation.getTag(); // remove from the local copy. int time = (Integer) nview.time.getTag(); mAnnotationProgressNoteMap.remove(time); mProgressBar.RemoveAnnotation(time); // remove from the database. VideoPlayerActivity.this.getContentResolver().delete( Notes.Note.CONTENT_URI, String.format("%s = ?", Notes.Note._ID), new String[] { Integer.toString(nview.id) }); UpdateNotesList(); } } mQuickAction.dismiss(); } }; /** * Event listener for <b>QuickAction</b> dismissed event. We set the current * editing annotation item to null. */ private PopupWindow.OnDismissListener mActionDismissed = new PopupWindow.OnDismissListener() { @Override public void onDismiss() { mEditingAnnotation = null; } }; // / // / INTERNAL CLASSES // / /** * Representation of a video file. */ private class VideoItem { public int mID; public String mUri; /** * Initializes a new instance of <b>VideoItem</b>. * * @param id * @param uri */ public VideoItem(int id, String uri) { mID = id; mUri = uri; } } }