/* * Copyright (C) 2012 Simon Robinson * * This file is part of Com-Me. * * Com-Me is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 3 of the * License, or (at your option) any later version. * * Com-Me is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General * Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Com-Me. * If not, see <http://www.gnu.org/licenses/>. */ package ac.robinson.mediatablet; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Hashtable; import java.util.Locale; import java.util.Map; import ac.robinson.mediatablet.activity.HomesteadBrowserActivity; import ac.robinson.mediatablet.activity.PeopleBrowserActivity; import ac.robinson.mediatablet.importing.ImportedFileParser; import ac.robinson.mediatablet.provider.MediaItem; import ac.robinson.mediatablet.provider.MediaManager; import ac.robinson.mediatablet.provider.MediaTabletProvider; import ac.robinson.mediatablet.provider.PersonItem; import ac.robinson.mediatablet.provider.PersonManager; import ac.robinson.mediautilities.FrameMediaContainer; import ac.robinson.mediautilities.MediaUtilities; import ac.robinson.mediautilities.SMILUtilities; import ac.robinson.util.IOUtilities; import ac.robinson.util.ImageCacheUtilities; import ac.robinson.util.StringUtilities; import ac.robinson.util.UIUtilities; import ac.robinson.util.UIUtilities.ReflectionTab; import ac.robinson.util.UIUtilities.ReflectionTabListener; import android.app.Activity; import android.app.AlertDialog; import android.content.ContentResolver; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Resources; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.EditText; import android.widget.Toast; public abstract class MediaViewerActivity extends MediaTabletActivity { private String mMediaInternalId; private String mMediaParentId; private File mMediaFile; private int mMediaType; private boolean mOwnerMode; abstract protected void initialiseView(Bundle savedInstanceState); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // load ids on screen rotation mMediaInternalId = null; mMediaParentId = null; if (savedInstanceState != null) { mMediaInternalId = savedInstanceState.getString(getString(R.string.extra_internal_id)); mMediaParentId = savedInstanceState.getString(getString(R.string.extra_parent_id)); } else { final Intent intent = getIntent(); if (intent != null) { mMediaInternalId = intent.getStringExtra(getString(R.string.extra_internal_id)); mMediaParentId = intent.getStringExtra(getString(R.string.extra_parent_id)); } } if (mMediaInternalId == null) { UIUtilities.showToast(MediaViewerActivity.this, R.string.error_loading_media); finish(); return; } MediaItem currentMediaItem = MediaManager.findMediaByInternalId(getContentResolver(), mMediaInternalId); mMediaFile = currentMediaItem.getFile(); mMediaType = currentMediaItem.getType(); if (mMediaFile == null || !mMediaFile.exists()) { UIUtilities.showToast(MediaViewerActivity.this, R.string.error_loading_media); finish(); return; } // mMediaParentId == null -> public media mode // mMediaParentId != null && mOwnerMode == true -> private mode // mMediaParentId != null && mOwnerMode == false -> private shared media mode mOwnerMode = false; ContentResolver contentResolver = getContentResolver(); UIUtilities.configureActionBar(this, false, false, R.string.title_playback, 0); if (mMediaParentId != null) { PersonItem currentPersonItem = PersonManager.findPersonByInternalId(contentResolver, mMediaParentId); mOwnerMode = !currentPersonItem.isLocked(); String personName = currentPersonItem == null ? null : currentPersonItem.getName(); String mediaTitle = currentPersonItem == null || personName == null ? getString(R.string.title_media_browser) : getString(mOwnerMode ? R.string.title_media_browser_private_personalised : R.string.title_media_browser_public_personalised, personName); String peopleTitle = currentPersonItem == null || personName == null ? getString(R.string.title_people_browser) : getString(R.string.title_people_browser_personalised, personName); UIUtilities.addActionBarTabs(this, new ReflectionTab[] { new ReflectionTab(R.id.intent_homestead_browser, R.drawable.ic_menu_homesteads, getString(R.string.title_homestead_browser)), new ReflectionTab(R.id.intent_people_browser, R.drawable.ic_menu_people, peopleTitle), new ReflectionTab(R.id.intent_media_browser, R.drawable.ic_menu_media, mediaTitle), new ReflectionTab(R.id.intent_media_item_viewer, android.R.drawable.ic_menu_info_details, getString(R.string.title_playback), true) }, mReflectionTabListener); } else { UIUtilities.addActionBarTabs(this, new ReflectionTab[] { new ReflectionTab(R.id.intent_homestead_browser, R.drawable.ic_menu_homesteads, getString(R.string.title_homestead_browser)), new ReflectionTab(R.id.intent_media_browser, R.drawable.ic_menu_public_media, getString(R.string.title_media_browser_public)), new ReflectionTab(R.id.intent_media_item_viewer, android.R.drawable.ic_menu_info_details, getString(R.string.title_playback), true) }, mReflectionTabListener); } initialiseView(savedInstanceState); // for API 11 and above, buttons are in the action bar - could use XML-v11 but maintenance is a hassle if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { findViewById(R.id.panel_media_viewer).setVisibility(View.GONE); } else { if (mMediaParentId == null || !mOwnerMode) { findViewById(R.id.button_media_view_view_people).setVisibility(View.GONE); findViewById(R.id.button_media_view_view_media).setVisibility(View.GONE); findViewById(R.id.button_media_view_view_public_media).setVisibility(View.GONE); findViewById(R.id.button_media_view_back).setVisibility(View.VISIBLE); } } } @Override public void onSaveInstanceState(Bundle savedInstanceState) { savedInstanceState.putString(getString(R.string.extra_internal_id), mMediaInternalId); savedInstanceState.putString(getString(R.string.extra_parent_id), mMediaParentId); super.onSaveInstanceState(savedInstanceState); } @Override protected void onDestroy() { ImageCacheUtilities.cleanupCache(); super.onDestroy(); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); if (mOwnerMode) { inflater.inflate(R.menu.share, menu); } inflater.inflate(R.menu.send, menu); if (mMediaParentId != null) { inflater.inflate(R.menu.public_media, menu); } inflater.inflate(R.menu.delete, menu); menu.findItem(R.id.menu_delete).setTitle(getString(R.string.menu_delete_media)); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_share: shareMedia(); return true; case R.id.menu_send: sendMedia(); return true; case R.id.menu_view_public_media: viewPublicMedia(); return true; case R.id.menu_delete: showDeleteMediaDialog(); return true; default: return super.onOptionsItemSelected(item); } } @Override protected void loadPreferences(SharedPreferences mediaTabletSettings) { } @Override protected String getCurrentPersonId() { return mOwnerMode ? mMediaParentId : PersonItem.UNKNOWN_PERSON_ID; } protected String getCurrentMediaId() { return mMediaInternalId; } protected File getCurrentMediaFile() { return mMediaFile; } protected int getCurrentMediaType() { return mMediaType; } private void sendFiles(ArrayList<Uri> filesToSend, String mimeType) { if (filesToSend == null || filesToSend.size() <= 0) { // TODO: show error (but remember it's from a background task, so we can't show a Toast) return; } // also see: http://stackoverflow.com/questions/2344768/ final Intent sendIntent = new Intent(Intent.ACTION_SEND_MULTIPLE); sendIntent.setType(mimeType); // application/smil+xml (or html), or video/quicktime, but then no bluetooth opt sendIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, filesToSend); startActivity(Intent.createChooser(sendIntent, getString(R.string.send_media_title))); // (single task mode) // startActivity(sendIntent); //no title (but does allow saving default...) } private void sendMedia() { if (MediaTablet.DIRECTORY_TEMP == null) { UIUtilities.showToast(MediaViewerActivity.this, R.string.export_missing_directory, true); return; } boolean canCopyToExternal = true; if (IOUtilities.isInternalPath(MediaTablet.DIRECTORY_TEMP.getAbsolutePath())) { canCopyToExternal = false; UIUtilities.showToast(MediaViewerActivity.this, R.string.export_potential_problem, true); } // important to keep awake to export because we only have one chance to display the export options // after creating mov or smil file (will be cancelled on screen unlock; Android is weird) // TODO: move to a better (e.g. notification bar) method of exporting? UIUtilities.acquireKeepScreenOn(getWindow()); ArrayList<Uri> filesToSend; String mimeType = "video/*"; MediaItem currentMediaItem = MediaManager.findMediaByInternalId(getContentResolver(), mMediaInternalId); if (currentMediaItem.getType() != MediaTabletProvider.TYPE_NARRATIVE) { filesToSend = new ArrayList<Uri>(); File currentFile = currentMediaItem.getFile(); File publicFile = currentFile; // can't send from private data directory if (IOUtilities.isInternalPath(currentFile.getAbsolutePath()) && canCopyToExternal) { try { publicFile = new File(MediaTablet.DIRECTORY_TEMP, currentFile.getName()); IOUtilities.copyFile(currentFile, publicFile); IOUtilities.setFullyPublic(publicFile); } catch (IOException e) { UIUtilities.showToast(MediaViewerActivity.this, R.string.error_sending_media); return; } } filesToSend.add(Uri.fromFile(publicFile)); switch (currentMediaItem.getType()) { case MediaTabletProvider.TYPE_IMAGE_BACK: case MediaTabletProvider.TYPE_IMAGE_FRONT: mimeType = "image/*"; break; case MediaTabletProvider.TYPE_AUDIO: mimeType = "audio/*"; break; case MediaTabletProvider.TYPE_VIDEO: break; case MediaTabletProvider.TYPE_TEXT: mimeType = "text/*"; break; case MediaTabletProvider.TYPE_UNKNOWN: default: mimeType = "unknown"; // TODO break; } } else { // TODO: extract and combine with MediaPhone version Resources res = getResources(); final ArrayList<FrameMediaContainer> smilContents = SMILUtilities.getSMILFrameList( currentMediaItem.getFile(), res.getInteger(R.integer.frame_narrative_sequence_increment), false, 0, false); // random name to counter repeat sending name issues final String exportName = String.format(Locale.ENGLISH, "%s-%s", getString(R.string.app_name).replaceAll("[^a-zA-Z0-9]+", "-").toLowerCase(Locale.ENGLISH), MediaTabletProvider.getNewInternalId().substring(0, 8)); final Map<Integer, Object> settings = new Hashtable<Integer, Object>(); settings.put(MediaUtilities.KEY_AUDIO_RESOURCE_ID, R.raw.ic_audio_playback); // some output settings settings.put(MediaUtilities.KEY_BACKGROUND_COLOUR, res.getColor(R.color.icon_background)); settings.put(MediaUtilities.KEY_TEXT_COLOUR_NO_IMAGE, res.getColor(R.color.icon_text_no_image)); settings.put(MediaUtilities.KEY_TEXT_COLOUR_WITH_IMAGE, res.getColor(R.color.icon_text_with_image)); settings.put(MediaUtilities.KEY_TEXT_BACKGROUND_COLOUR, res.getColor(R.color.icon_text_background)); settings.put(MediaUtilities.KEY_TEXT_SPACING, res.getDimensionPixelSize(R.dimen.export_icon_text_padding)); settings.put(MediaUtilities.KEY_TEXT_CORNER_RADIUS, res.getDimensionPixelSize(R.dimen.export_icon_text_corner_radius)); settings.put(MediaUtilities.KEY_TEXT_BACKGROUND_SPAN_WIDTH, Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB); // TODO: do we want to do getDimensionPixelSize for export? settings.put(MediaUtilities.KEY_MAX_TEXT_FONT_SIZE, res.getDimensionPixelSize(R.dimen.export_maximum_text_size)); settings.put(MediaUtilities.KEY_MAX_TEXT_CHARACTERS_PER_LINE, res.getInteger(R.integer.export_maximum_text_characters_per_line)); settings.put(MediaUtilities.KEY_MAX_TEXT_HEIGHT_WITH_IMAGE, res.getDimensionPixelSize(R.dimen.export_maximum_text_height_with_image)); settings.put(MediaUtilities.KEY_OUTPUT_WIDTH, res.getInteger(R.integer.export_smil_width)); settings.put(MediaUtilities.KEY_OUTPUT_HEIGHT, res.getInteger(R.integer.export_smil_height)); settings.put(MediaUtilities.KEY_PLAYER_BAR_ADJUSTMENT, res.getInteger(R.integer.export_smil_player_bar_adjustment)); filesToSend = SMILUtilities.generateNarrativeSMIL(getResources(), new File(MediaTablet.DIRECTORY_TEMP, exportName + MediaUtilities.SMIL_FILE_EXTENSION), smilContents, settings); } sendFiles(filesToSend, mimeType); } private void shareMedia() { final CharSequence[] publicMediaOptions = { getString(R.string.share_media_private), getString(R.string.share_media_choose), getString(R.string.share_media_public) }; MediaItem currentMediaItem = MediaManager.findMediaByInternalId(getContentResolver(), mMediaInternalId); AlertDialog.Builder builder = new AlertDialog.Builder(MediaViewerActivity.this); builder.setTitle(R.string.share_media_title); // builder.setMessage(R.string.share_media_hint); // breaks the dialog builder.setIcon(android.R.drawable.ic_dialog_info); builder.setNegativeButton(R.string.button_cancel, null); builder.setSingleChoiceItems(publicMediaOptions, (currentMediaItem.isPubliclyShared() ? 2 : 0), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int item) { if (item == 0 || item == 2) { // no need to update the icon (will be done when next viewed) ContentResolver contentResolver = getContentResolver(); MediaItem sharedMediaItem = MediaManager.findMediaByInternalId(contentResolver, mMediaInternalId); int newVisibility = item == 0 ? MediaItem.MEDIA_PRIVATE : MediaItem.MEDIA_PUBLIC; // TODO: should we share each individual narrative component? // if (sharedMediaItem.getType() == MediaTabletProvider.TYPE_NARRATIVE) { // ArrayList<FrameMediaContainer> smilContents = SMILUtilities.getSMILFrameList( // sharedMediaItem.getFile(), 1, false, 0, false); // // for (FrameMediaContainer frame : smilContents) { // // if (frame.mImagePath != null) { // final MediaItem imageMediaItem = MediaManager.findMediaByInternalId( // contentResolver, MediaItem.getInternalId(frame.mImagePath)); // imageMediaItem.setPubliclySharedStatus(newVisibility); // MediaManager.updateMedia(contentResolver, imageMediaItem); // } // // for (String mediaPath : frame.mAudioPaths) { // final MediaItem audioMediaItem = MediaManager.findMediaByInternalId( // contentResolver, MediaItem.getInternalId(mediaPath)); // audioMediaItem.setPubliclySharedStatus(newVisibility); // MediaManager.updateMedia(contentResolver, audioMediaItem); // } // // // text is stored in the narrative file itself - no need to share // } // } sharedMediaItem.setPubliclySharedStatus(newVisibility); MediaManager.updateMedia(contentResolver, sharedMediaItem); } else { Intent showPeopleIntent = new Intent(MediaViewerActivity.this, PeopleBrowserActivity.class); startActivityForResult(showPeopleIntent, R.id.intent_people_browser); } dialog.dismiss(); } }); AlertDialog alert = builder.create(); alert.show(); } private void sendShareMedia() { if (mMediaParentId == null || !mOwnerMode) { sendMedia(); } else { final CharSequence[] sendShareOptions = { getString(R.string.menu_send), getString(R.string.menu_share), getString(R.string.button_delete) }; AlertDialog.Builder builder = new AlertDialog.Builder(MediaViewerActivity.this); builder.setTitle(R.string.edit_media_title); // builder.setMessage(R.string.edit_media_hint); // breaks the dialog builder.setIcon(android.R.drawable.ic_dialog_info); builder.setNegativeButton(R.string.button_cancel, null); builder.setItems(sendShareOptions, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int item) { switch (item) { case 0: sendMedia(); break; case 1: shareMedia(); break; case 2: showDeleteMediaDialog(); break; } } }); AlertDialog alert = builder.create(); alert.show(); } } private void showDeleteMediaDialog() { if (mOwnerMode) { AlertDialog.Builder builder = new AlertDialog.Builder(MediaViewerActivity.this); builder.setTitle(R.string.delete_media_confirmation); builder.setMessage(R.string.delete_media_hint); builder.setIcon(android.R.drawable.ic_dialog_alert); builder.setNegativeButton(R.string.button_cancel, null); builder.setPositiveButton(R.string.button_delete, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int whichButton) { deleteMedia(); } }); AlertDialog alert = builder.create(); alert.show(); } else { LayoutInflater inflater = LayoutInflater.from(MediaViewerActivity.this); final View textEntryView = inflater.inflate(R.layout.password_input, null); AlertDialog.Builder builder = new AlertDialog.Builder(MediaViewerActivity.this); builder.setMessage(R.string.delete_media_password_prompt).setCancelable(false).setView(textEntryView) .setPositiveButton(R.string.button_delete, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { if (MediaTablet.ADMINISTRATOR_PASSWORD.equals(StringUtilities .sha1Hash(((EditText) textEntryView.findViewById(R.id.text_password_entry)) .getText().toString()))) { deleteMedia(); } else { UIUtilities.showToast(MediaViewerActivity.this, R.string.delete_media_password_incorrect); } } }).setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }); builder.create(); builder.show(); } } private void deleteMedia() { ContentResolver contentResolver = getContentResolver(); MediaItem mediaToDelete = MediaManager.findMediaByInternalId(getContentResolver(), mMediaInternalId); mediaToDelete.setDeleted(true); MediaManager.updateMedia(contentResolver, mediaToDelete); UIUtilities.showToast(MediaViewerActivity.this, R.string.delete_media_deleted); finish(); } private void viewHomesteads() { Intent intent = new Intent(MediaViewerActivity.this, HomesteadBrowserActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); finish(); } private void viewPeople() { final Intent resultIntent = new Intent(); resultIntent.putExtra(getString(R.string.extra_finish_activity), true); setResult(Activity.RESULT_OK, resultIntent); // exit media browser too finish(); } public void handleButtonClicks(View currentButton) { switch (currentButton.getId()) { case R.id.button_media_view_share_media: sendShareMedia(); break; case R.id.button_media_view_view_homesteads: viewHomesteads(); case R.id.button_media_view_view_people: viewPeople(); break; case R.id.button_media_view_view_media: case R.id.button_media_view_back: finish(); break; case R.id.button_media_view_view_public_media: viewPublicMedia(); break; } } @Override public void onActivityResult(int requestCode, int resultCode, Intent resultIntent) { switch (requestCode) { case R.id.intent_media_browser: if (resultCode == Activity.RESULT_OK && resultIntent != null) { if (resultIntent.getBooleanExtra(getString(R.string.extra_finish_activity), false)) { if (resultIntent.getBooleanExtra(getString(R.string.extra_finish_parent_activities), false)) { viewHomesteads(); } else { finish(); } } } break; case R.id.intent_people_browser: if (resultCode == Activity.RESULT_OK && resultIntent != null && resultIntent.hasExtra(getString(R.string.extra_selected_items))) { ContentResolver contentResolver = getContentResolver(); MediaItem sharedMediaItem = MediaManager.findMediaByInternalId(contentResolver, mMediaInternalId); ArrayList<FrameMediaContainer> smilContents = null; if (sharedMediaItem.getType() == MediaTabletProvider.TYPE_NARRATIVE) { smilContents = SMILUtilities.getSMILFrameList(sharedMediaItem.getFile(), 1, false, 0, false); } String[] selectedPeople = resultIntent .getStringArrayExtra(getString(R.string.extra_selected_items)); int numPeopleShared = selectedPeople.length; for (String shareDestination : selectedPeople) { if (!shareDestination.equals(mMediaParentId)) { // don't send to self if (sharedMediaItem.getType() == MediaTabletProvider.TYPE_NARRATIVE) { ImportedFileParser.duplicateSMILElements(contentResolver, smilContents, sharedMediaItem.getFile(), shareDestination, MediaItem.MEDIA_PRIVATE, false); } else { final MediaItem newMediaItem = MediaItem.fromExisting(sharedMediaItem, MediaTabletProvider.getNewInternalId(), shareDestination, System.currentTimeMillis()); newMediaItem.setPubliclySharedStatus(MediaItem.MEDIA_PRIVATE); try { IOUtilities.copyFile(sharedMediaItem.getFile(), newMediaItem.getFile()); MediaManager.addMedia(contentResolver, newMediaItem); } catch (IOException e) { } } } else { numPeopleShared -= 1; } } // can't use UIUtilities for formatted Toast strings Toast.makeText( MediaViewerActivity.this, getResources().getQuantityString(R.plurals.share_media_choose_completed, numPeopleShared, numPeopleShared), Toast.LENGTH_SHORT).show(); } break; default: super.onActivityResult(requestCode, resultCode, resultIntent); } } private ReflectionTabListener mReflectionTabListener = new ReflectionTabListener() { @Override public void onTabSelected(int tabId) { switch (tabId) { case R.id.intent_homestead_browser: viewHomesteads(); break; case R.id.intent_people_browser: viewPeople(); break; case R.id.intent_media_browser: finish(); break; default: break; } } @Override public void onTabReselected(int tabId) { } @Override public void onTabUnselected(int tabId) { } }; }