// Copyright 2010 Google Inc. // Copyright 2011 NPR // // 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.npr.android.news; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import android.view.*; import org.npr.android.util.PlaylistEntry; import org.npr.android.util.PlaylistRepository; import org.npr.api.ApiConstants; import android.app.Activity; import android.app.AlertDialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioManager; import android.os.Bundle; import android.text.method.LinkMovementMethod; import android.util.Log; import android.view.View.OnClickListener; import android.view.ViewGroup.LayoutParams; // import com.crittercism.app.Crittercism; import android.widget.AdapterView; import android.widget.ImageButton; import android.widget.ProgressBar; import android.widget.TextView; /** * @author mfrederick@google.com (Michael Frederick) * A base class for all Activities that want to display the default layout, * including the PlaylistView. */ public abstract class RootActivity extends Activity implements Refreshable, OnClickListener { private static final String LOG_TAG = RootActivity.class.getName(); private NavigationView navigationView; private PlaylistView playlistView; private ProgressBar progressIndicator; private BroadcastReceiver updateReceiver; @Override protected void onCreate(Bundle savedInstanceState) { requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); super.onCreate(savedInstanceState); setContentView(R.layout.main); // Override the normal volume controls so that the user can alter the volume // when a stream is not playing. setVolumeControlStream(AudioManager.STREAM_MUSIC); ViewGroup titleFrame = (ViewGroup) findViewById(R.id.TitleContent); navigationView = new NavigationView(this); titleFrame.addView(navigationView, new ViewGroup.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); navigationView.setVisibility(View.GONE); ImageButton mainSearchButton = (ImageButton) findViewById(R.id.MainSearchButton); mainSearchButton.setOnClickListener(this); ImageButton mainNavButton = (ImageButton) findViewById(R.id.MainNavButton); mainNavButton.setOnClickListener(this); playlistView = new PlaylistView(this); titleFrame.addView(playlistView, new ViewGroup.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); progressIndicator = (ProgressBar) findViewById(R.id.WindowProgressIndicator); // Initializes the crittercism library /*Crittercism.init(getApplicationContext(), "", // Code removed from open source true); */ } protected PlaylistView getPlaylistView() { return playlistView; } protected void startIndeterminateProgressIndicator() { progressIndicator.setVisibility(View.VISIBLE); // setSupportProgressBarIndeterminateVisibility(true); // Once we move to a proper action bar } protected void stopIndeterminateProgressIndicator() { progressIndicator.setVisibility(View.INVISIBLE); // setSupportProgressBarIndeterminateVisibility(false); // Once we move to a proper action bar } @Override protected void onStart() { super.onStart(); if (!ApiConstants.instance().apiKeyIsValid()) { final AlertDialog dialog = new AlertDialog.Builder(this) .setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { finish(); } }) .setMessage(R.string.msg_api_key_error) .create(); dialog.show(); // Make the TextView clickable. Must be called after show() // http://stackoverflow.com/questions/1997328/android-clickable-hyperlinks-in-alertdialog ((TextView) dialog.findViewById(android.R.id.message)) .setMovementMethod(LinkMovementMethod.getInstance()); } bringPlayerNavToFront(); } protected void bringPlayerNavToFront() { // Navigation on top of player, player on top of content playlistView.bringToFront(); navigationView.bringToFront(); } @Override public boolean isRefreshable() { return false; } @Override public void refresh() { } @Override public void finish() { super.finish(); noAnimation(); } protected void startActivityWithoutAnimation(Intent i) { startActivity(i); noAnimation(); } /** * Prevents the default animation on the pending transition. Only works on * SDK version 5 and up, but may be safely called from any version. */ protected void noAnimation() { try { Method overridePendingTransition = Activity.class.getMethod("overridePendingTransition", new Class[]{ int.class, int.class}); overridePendingTransition.invoke(this, 0, 0); } catch (SecurityException e) { Log.w(LOG_TAG, "", e); } catch (NoSuchMethodException e) { // Don't log an error here; we anticipate an error on SDK < 5 } catch (IllegalArgumentException e) { Log.w(LOG_TAG, "", e); } catch (IllegalAccessException e) { Log.w(LOG_TAG, "", e); } catch (InvocationTargetException e) { Log.w(LOG_TAG, "", e); } } /** * Tells the player to start playing the playlist entry provided and * updates the display to reflect the actions. * * @param entry The PlaylistEntry to play */ protected void playEntryNow(PlaylistEntry entry) { playlistView.playEntryNow(entry); } /** * Tells the player to start playing the playable and stop if/when * the playable finishes. The player will not continue on to the items * in the playlist. The display is updated to reflect the actions. * * @param playable A Playable stream or podcast to play. */ protected void playSingleNow(Playable playable) { if (updateReceiver != null) { unregisterReceiver(updateReceiver); updateReceiver = null; } updateReceiver = new PlaybackUpdateReceiver(); registerReceiver(updateReceiver, new IntentFilter(PlaybackService.SERVICE_UPDATE_NAME)); playlistView.playSingleNow(playable); } private class PlaybackUpdateReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { int duration = intent.getIntExtra(PlaybackService.EXTRA_DURATION, 1); if (duration != 1) { stopIndeterminateProgressIndicator(); if (updateReceiver != null) { unregisterReceiver(updateReceiver); updateReceiver = null; } } } } @Override public boolean onCreateOptionsMenu(Menu menu) { showHideNavigationMenu(); // We're showing our own menu, so return false to keep the system one // from being displayed return false; } @Override public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { Log.d(LOG_TAG, "Creating context menu for list items"); super.onCreateContextMenu(menu, view, menuInfo); MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.playlist_context_menu, menu); // Set the menu header to the name of the item in the playlist try { AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; View nameText = info.targetView.findViewById(R.id.playlist_item_title); if (nameText != null && nameText instanceof TextView) { menu.setHeaderTitle(((TextView) nameText).getText()); } } catch (ClassCastException e) { Log.e(LOG_TAG, "MenuInfo was an unexpected type", e); } } @Override public boolean onContextItemSelected(MenuItem item) { AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); Log.d(LOG_TAG, "Context menu item clicked for position " + info.position); switch (item.getItemId()) { case R.id.PlaylistReadEntry: PlaylistEntry readEntry = playlistView.getPlaylistEntryAtPosition(info.position); if (readEntry != null) { playlistView.setExpanded(false); Intent i = new Intent(this, NewsStoryActivity.class); i.putExtra(Constants.EXTRA_STORY_ID, readEntry.storyID); startActivityWithoutAnimation(i); } return true; case R.id.PlaylistRemoveEntry: PlaylistEntry removeEntry = playlistView.getPlaylistEntryAtPosition(info.position); if (removeEntry != null) { PlaylistRepository repository = new PlaylistRepository(getApplicationContext(), getContentResolver()); repository.delete(removeEntry); playlistView.updatePlaylist(); } return true; default: return super.onContextItemSelected(item); } } @Override public void onClick(View v) { switch (v.getId()) { case R.id.MainSearchButton: startActivityWithoutAnimation(new Intent(this, SearchActivity.class)); break; case R.id.MainNavButton: showHideNavigationMenu(); break; } } private void showHideNavigationMenu() { if (navigationView.getVisibility() == View.GONE) { navigationView.setVisibility(View.VISIBLE); navigationView.requestFocus(); } else { navigationView.setVisibility(View.GONE); } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) { if (navigationView.getVisibility() == View.VISIBLE) { navigationView.setVisibility(View.GONE); return true; } else if (playlistView.isExpanded()) { playlistView.setExpanded(false); return true; } } return super.onKeyDown(keyCode, event); } public String getActiveId() { return playlistView.getActiveId(); } }