-Changed play queue item building to shrink thumbnail before caching.

-Renamed refactor directory in player to helper.
-Fixed background player notification update causing lag on older spec models.
-Fixed service activity theme not changing after user setting is changed.
-Fixed NPE on popup player fling to close.
-Fixed audio reactor volume and max volume mixup.
-Added correct toast for each player error case.
-Fixed button coloring for play queue service activity on landscape.
-Changed title and uploader text to marquee for vertical service activity.
-Removed cache clearing on every thumbnail load.
This commit is contained in:
John Zhen Mo 2017-10-28 10:08:01 -07:00
parent f284a799ef
commit 1fb3774e03
19 changed files with 248 additions and 223 deletions

View File

@ -34,7 +34,6 @@ import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.RemoteViews;
import android.widget.Toast;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
@ -46,12 +45,12 @@ import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.event.PlayerEventListener;
import org.schabi.newpipe.player.refactor.LockManager;
import org.schabi.newpipe.player.helper.LockManager;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.ThemeHelper;
import static org.schabi.newpipe.player.refactor.PlayerHelper.getTimeString;
import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
/**
@ -163,9 +162,7 @@ public final class BackgroundPlayer extends Service {
private void onScreenOnOff(boolean on) {
if (DEBUG) Log.d(TAG, "onScreenOnOff() called with: on = [" + on + "]");
shouldUpdateOnProgress = on;
if (on) {
basePlayerImpl.triggerProgressUpdate();
}
basePlayerImpl.triggerProgressUpdate();
}
/*//////////////////////////////////////////////////////////////////////////
@ -207,9 +204,9 @@ public final class BackgroundPlayer extends Service {
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_REPEAT), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationFRewind,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationFForward,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationFForward,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT));
setRepeatModeIcon(remoteViews, basePlayerImpl.getRepeatMode());
}
@ -299,7 +296,6 @@ public final class BackgroundPlayer extends Service {
updateNotification(-1);
}
clearThumbnailCache();
}
@Override
@ -317,7 +313,9 @@ public final class BackgroundPlayer extends Service {
@Override
public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) {
updateProgress(currentProgress, duration, bufferPercent);
if (!shouldUpdateOnProgress) return;
resetNotification();
if (bigNotRemoteView != null) {
bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false);
bigNotRemoteView.setTextViewText(R.id.notificationTime, getTimeString(currentProgress) + " / " + getTimeString(duration));
@ -325,7 +323,6 @@ public final class BackgroundPlayer extends Service {
if (notRemoteView != null) {
notRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false);
}
updateNotification(-1);
}
@ -348,29 +345,6 @@ public final class BackgroundPlayer extends Service {
if (bigNotRemoteView != null) bigNotRemoteView.setImageViewBitmap(R.id.notificationCover, null);
}
@Override
public void onRecoverableError(Exception exception) {
exception.printStackTrace();
if (errorToast == null) {
errorToast = Toast.makeText(context, R.string.player_audio_failure, Toast.LENGTH_SHORT);
errorToast.show();
}
}
@Override
public void onUnrecoverableError(Exception exception) {
exception.printStackTrace();
if (errorToast != null) {
errorToast.cancel();
}
errorToast = Toast.makeText(context, R.string.player_unexpected_failure, Toast.LENGTH_SHORT);
errorToast.show();
shutdown();
}
/*//////////////////////////////////////////////////////////////////////////
// ExoPlayer Listener
//////////////////////////////////////////////////////////////////////////*/
@ -388,6 +362,7 @@ public final class BackgroundPlayer extends Service {
@Override
public void onRepeatModeChanged(int i) {
resetNotification();
setRepeatModeIcon(notRemoteView, i);
setRepeatModeIcon(bigNotRemoteView, i);
updateNotification(-1);

View File

@ -65,12 +65,13 @@ import com.google.android.exoplayer2.util.Util;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.playback.MediaSourceManager;
import org.schabi.newpipe.player.playback.PlaybackListener;
import org.schabi.newpipe.player.refactor.AudioReactor;
import org.schabi.newpipe.player.refactor.CacheFactory;
import org.schabi.newpipe.player.refactor.LoadController;
import org.schabi.newpipe.player.helper.AudioReactor;
import org.schabi.newpipe.player.helper.CacheFactory;
import org.schabi.newpipe.player.helper.LoadController;
import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.PlayQueueAdapter;
import org.schabi.newpipe.playlist.PlayQueueItem;
@ -85,7 +86,7 @@ import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Predicate;
import static org.schabi.newpipe.player.refactor.PlayerHelper.getTimeString;
import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
/**
* Base for the players, joining the common properties
@ -194,7 +195,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
public void initListeners() {}
protected Disposable getProgressReactor() {
private Disposable getProgressReactor() {
return Observable.interval(PROGRESS_LOOP_INTERVAL, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.filter(new Predicate<Long>() {
@ -249,7 +250,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
playbackManager = new MediaSourceManager(this, playQueue);
if (playQueueAdapter != null) playQueueAdapter.dispose();
playQueueAdapter = new PlayQueueAdapter(playQueue);
playQueueAdapter = new PlayQueueAdapter(context, playQueue);
}
public void initThumbnail(final String url) {
@ -536,7 +537,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
case Player.STATE_ENDED: // 4
// Ensure the current window has actually ended
// since single windows that are still loading may produce an ended state
if (simpleExoPlayer.getDuration() > 0 && simpleExoPlayer.getCurrentPosition() >= simpleExoPlayer.getDuration()) {
if (isCurrentWindowValid() && simpleExoPlayer.getCurrentPosition() >= simpleExoPlayer.getDuration()) {
changeState(STATE_COMPLETED);
isPrepared = false;
}
@ -549,10 +550,9 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
* There are multiple types of errors: <br><br>
*
* {@link ExoPlaybackException#TYPE_SOURCE TYPE_SOURCE}: <br><br>
* If the current {@link com.google.android.exoplayer2.Timeline.Window window} has
* duration and position greater than 0, then we know the current window is working correctly
* and the error is produced by transitioning into a bad window, therefore we report an error
* to the play queue based on if the current error can be skipped.
* If the current {@link com.google.android.exoplayer2.Timeline.Window window} is valid,
* then we know the error is produced by transitioning into a bad window, therefore we report
* an error to the play queue based on if the current error can be skipped.
*
* This is done because ExoPlayer reports the source exceptions before window is
* transitioned on seamless playback.
@ -579,9 +579,8 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
switch (error.type) {
case ExoPlaybackException.TYPE_SOURCE:
final boolean skippable = simpleExoPlayer.getDuration() >= 0 && simpleExoPlayer.getCurrentPosition() >= 0;
playQueue.error(skippable);
onRecoverableError(error);
playQueue.error(isCurrentWindowValid());
onStreamError(error);
break;
case ExoPlaybackException.TYPE_UNEXPECTED:
onRecoverableError(error);
@ -670,9 +669,35 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
// General Player
//////////////////////////////////////////////////////////////////////////*/
public abstract void onRecoverableError(Exception exception);
public void onStreamError(Exception exception) {
exception.printStackTrace();
public abstract void onUnrecoverableError(Exception exception);
if (errorToast == null) {
errorToast = Toast.makeText(context, R.string.player_stream_failure, Toast.LENGTH_SHORT);
errorToast.show();
}
}
public void onRecoverableError(Exception exception) {
exception.printStackTrace();
if (errorToast == null) {
errorToast = Toast.makeText(context, R.string.player_recoverable_failure, Toast.LENGTH_SHORT);
errorToast.show();
}
}
public void onUnrecoverableError(Exception exception) {
exception.printStackTrace();
if (errorToast != null) {
errorToast.cancel();
}
errorToast = Toast.makeText(context, R.string.player_unrecoverable_failure, Toast.LENGTH_SHORT);
errorToast.show();
shutdown();
}
public void onPrepared(boolean playWhenReady) {
if (DEBUG) Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]");
@ -754,6 +779,11 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
simpleExoPlayer.seekTo(progress);
}
public boolean isCurrentWindowValid() {
return simpleExoPlayer != null && simpleExoPlayer.getDuration() >= 0
&& simpleExoPlayer.getCurrentPosition() >= 0;
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/

View File

@ -23,7 +23,6 @@ import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.media.AudioManager;
import android.os.Build;
@ -50,7 +49,7 @@ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.player.refactor.PlayerHelper;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.playlist.PlayQueueItemBuilder;
import org.schabi.newpipe.playlist.PlayQueueItemHolder;
@ -288,12 +287,6 @@ public final class MainVideoPlayer extends Activity {
screenRotationButton.setOnClickListener(this);
}
@Override
public void onThumbnailReceived(Bitmap thumbnail) {
super.onThumbnailReceived(thumbnail);
clearThumbnailCache();
}
/*//////////////////////////////////////////////////////////////////////////
// ExoPlayer Video Listener
//////////////////////////////////////////////////////////////////////////*/
@ -439,29 +432,6 @@ public final class MainVideoPlayer extends Activity {
if (isPlaying()) hideControls(300, 0);
}
@Override
public void onRecoverableError(Exception exception) {
exception.printStackTrace();
if (errorToast == null) {
errorToast = Toast.makeText(context, R.string.player_video_failure, Toast.LENGTH_SHORT);
errorToast.show();
}
}
@Override
public void onUnrecoverableError(Exception exception) {
exception.printStackTrace();
if (errorToast != null) {
errorToast.cancel();
}
errorToast = Toast.makeText(context, R.string.player_unexpected_failure, Toast.LENGTH_SHORT);
errorToast.show();
shutdown();
}
@Override
protected int getDefaultResolutionIndex(final List<VideoStream> sortedVideos) {
return ListHelper.getDefaultResolutionIndex(context, sortedVideos);
@ -763,11 +733,12 @@ public final class MainVideoPlayer extends Activity {
if (e1.getX() > playerImpl.getRootView().getWidth() / 2) {
double floor = Math.floor(up ? stepVolume : -stepVolume);
currentVolume = (int) (playerImpl.getAudioReactor().getMaxVolume() + floor);
currentVolume = (int) (playerImpl.getAudioReactor().getVolume() + floor);
if (currentVolume >= maxVolume) currentVolume = maxVolume;
if (currentVolume <= minVolume) currentVolume = (int) minVolume;
playerImpl.getAudioReactor().setMaxVolume(currentVolume);
playerImpl.getAudioReactor().setVolume(currentVolume);
currentVolume = playerImpl.getAudioReactor().getVolume();
if (DEBUG) Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume);
final String volumeText = volumeUnicode + " " + Math.round((((float) currentVolume) / maxVolume) * 100) + "%";
playerImpl.getVolumeTextView().setText(volumeText);

View File

@ -66,7 +66,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.player.event.PlayerEventListener;
import org.schabi.newpipe.player.old.PlayVideoActivity;
import org.schabi.newpipe.player.refactor.LockManager;
import org.schabi.newpipe.player.helper.LockManager;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.playlist.SinglePlayQueue;
import org.schabi.newpipe.report.ErrorActivity;
@ -85,7 +85,7 @@ import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
import static org.schabi.newpipe.player.refactor.PlayerHelper.isUsingOldPlayer;
import static org.schabi.newpipe.player.helper.PlayerHelper.isUsingOldPlayer;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
/**
@ -366,7 +366,7 @@ public final class PopupVideoPlayer extends Service {
}
private void updatePopupSize(int width, int height) {
//if (DEBUG) Log.d(TAG, "updatePopupSize() called with: width = [" + width + "], height = [" + height + "]");
if (DEBUG) Log.d(TAG, "updatePopupSize() called with: width = [" + width + "], height = [" + height + "]");
width = (int) (width > maximumWidth ? maximumWidth : width < minimumWidth ? minimumWidth : width);
@ -440,7 +440,6 @@ public final class PopupVideoPlayer extends Service {
updateNotification(-1);
}
clearThumbnailCache();
}
@Override
@ -481,29 +480,6 @@ public final class PopupVideoPlayer extends Service {
if (isPlaying()) hideControls(500, 0);
}
@Override
public void onRecoverableError(Exception exception) {
exception.printStackTrace();
if (errorToast == null) {
errorToast = Toast.makeText(context, R.string.player_video_failure, Toast.LENGTH_SHORT);
errorToast.show();
}
}
@Override
public void onUnrecoverableError(Exception exception) {
exception.printStackTrace();
if (errorToast != null) {
errorToast.cancel();
}
errorToast = Toast.makeText(context, R.string.player_unexpected_failure, Toast.LENGTH_SHORT);
errorToast.show();
shutdown();
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
super.onStopTrackingTouch(seekBar);
@ -737,7 +713,7 @@ public final class PopupVideoPlayer extends Service {
public boolean onDoubleTap(MotionEvent e) {
if (DEBUG)
Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY());
if (!playerImpl.isPlaying() || !playerImpl.isPlayerReady()) return false;
if (playerImpl == null || !playerImpl.isPlaying() || !playerImpl.isPlayerReady()) return false;
if (e.getX() > popupWidth / 2) {
playerImpl.onFastForward();
@ -751,7 +727,7 @@ public final class PopupVideoPlayer extends Service {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
if (DEBUG) Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
if (playerImpl.getPlayer() == null) return false;
if (playerImpl == null || playerImpl.getPlayer() == null) return false;
playerImpl.onVideoPlayPause();
return true;
}
@ -776,7 +752,7 @@ public final class PopupVideoPlayer extends Service {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (isResizing) return super.onScroll(e1, e2, distanceX, distanceY);
if (isResizing || playerImpl == null) return super.onScroll(e1, e2, distanceX, distanceY);
if (playerImpl.getCurrentState() != BasePlayer.STATE_BUFFERING
&& (!isMoving || playerImpl.getControlsRoot().getAlpha() != 1f)) playerImpl.showControls(0);
@ -807,6 +783,7 @@ public final class PopupVideoPlayer extends Service {
private void onScrollEnd() {
if (DEBUG) Log.d(TAG, "onScrollEnd() called");
if (playerImpl == null) return;
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == BasePlayer.STATE_PLAYING) {
playerImpl.hideControls(300, VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME);
}
@ -814,6 +791,7 @@ public final class PopupVideoPlayer extends Service {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (playerImpl == null) return false;
if (Math.abs(velocityX) > SHUTDOWN_FLING_VELOCITY) {
if (DEBUG) Log.d(TAG, "Popup close fling velocity= " + velocityX);
onClose();
@ -825,6 +803,7 @@ public final class PopupVideoPlayer extends Service {
@Override
public boolean onTouch(View v, MotionEvent event) {
gestureDetector.onTouchEvent(event);
if (playerImpl == null) return false;
if (event.getPointerCount() == 2 && !isResizing) {
if (DEBUG) Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing.");
playerImpl.showAndAnimateControl(-1, true);

View File

@ -36,8 +36,8 @@ import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper;
import static org.schabi.newpipe.player.refactor.PlayerHelper.formatPitch;
import static org.schabi.newpipe.player.refactor.PlayerHelper.formatSpeed;
import static org.schabi.newpipe.player.helper.PlayerHelper.formatPitch;
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
public abstract class ServicePlayerActivity extends AppCompatActivity
implements PlayerEventListener, SeekBar.OnSeekBarChangeListener, View.OnClickListener {
@ -48,7 +48,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
protected BasePlayer player;
private boolean seeking;
private boolean redraw;
////////////////////////////////////////////////////////////////////////////
// Views
////////////////////////////////////////////////////////////////////////////
@ -119,6 +119,15 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
bind();
}
@Override
protected void onResume() {
super.onResume();
if (redraw) {
recreate();
redraw = false;
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_play_queue, menu);
@ -136,6 +145,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
return true;
case R.id.action_settings:
NavigationHelper.openSettings(this);
redraw = true;
return true;
case R.id.action_system_audio:
startActivity(new Intent(Settings.ACTION_SOUND_SETTINGS));
@ -228,6 +238,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
metadataArtist = rootView.findViewById(R.id.artist_name);
metadata.setOnClickListener(this);
metadataTitle.setSelected(true);
metadataArtist.setSelected(true);
}
private void buildSeekBar() {
@ -452,7 +464,6 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
onStateChanged(state);
onPlayModeChanged(repeatMode, shuffled);
onPlaybackParameterChanged(parameters);
scrollToSelected();
}
@Override

View File

@ -65,8 +65,8 @@ import org.schabi.newpipe.util.ListHelper;
import java.util.ArrayList;
import java.util.List;
import static org.schabi.newpipe.player.refactor.PlayerHelper.formatSpeed;
import static org.schabi.newpipe.player.refactor.PlayerHelper.getTimeString;
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
/**

View File

@ -1,4 +1,4 @@
package org.schabi.newpipe.player.refactor;
package org.schabi.newpipe.player.helper;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@ -17,7 +17,6 @@ import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.decoder.DecoderCounters;
@SuppressWarnings({"WeakerAccess", "unused"})
public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, AudioRendererEventListener {
private static final String TAG = "AudioFocusReactor";
@ -32,25 +31,22 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, Au
private final Context context;
private final AudioManager audioManager;
private AudioFocusRequest request;
private final boolean isResumeAfterAudioFocusGain;
private final AudioFocusRequest request;
public AudioReactor(@NonNull final Context context, @NonNull final SimpleExoPlayer player) {
this.player = player;
this.context = context;
this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
this.isResumeAfterAudioFocusGain = PlayerHelper.isResumeAfterAudioFocusGain(context);
player.setAudioDebugListener(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (shouldBuildFocusRequest()) {
request = new AudioFocusRequest.Builder(FOCUS_GAIN_TYPE)
.setAcceptsDelayedFocusGain(true)
.setWillPauseWhenDucked(true)
.setOnAudioFocusChangeListener(this)
.build();
} else {
request = null;
}
}
@ -59,7 +55,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, Au
//////////////////////////////////////////////////////////////////////////*/
public void requestAudioFocus() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (shouldBuildFocusRequest()) {
audioManager.requestAudioFocus(request);
} else {
audioManager.requestAudioFocus(this, STREAM_TYPE, FOCUS_GAIN_TYPE);
@ -67,21 +63,29 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, Au
}
public void abandonAudioFocus() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (shouldBuildFocusRequest()) {
audioManager.abandonAudioFocusRequest(request);
} else {
audioManager.abandonAudioFocus(this);
}
}
public int getVolume() {
return audioManager.getStreamVolume(STREAM_TYPE);
}
public int getMaxVolume() {
return audioManager.getStreamMaxVolume(STREAM_TYPE);
}
public void setMaxVolume(final int volume) {
public void setVolume(final int volume) {
audioManager.setStreamVolume(STREAM_TYPE, volume, 0);
}
private boolean shouldBuildFocusRequest() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
}
/*//////////////////////////////////////////////////////////////////////////
// AudioFocus
//////////////////////////////////////////////////////////////////////////*/
@ -108,7 +112,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, Au
player.setVolume(DUCK_AUDIO_TO);
animateAudio(DUCK_AUDIO_TO, 1f, DUCK_DURATION);
if (isResumeAfterAudioFocusGain) {
if (PlayerHelper.isResumeAfterAudioFocusGain(context)) {
player.setPlayWhenReady(true);
}
}
@ -159,6 +163,8 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, Au
@Override
public void onAudioSessionId(int i) {
if (!PlayerHelper.isUsingDSP(context)) return;
final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, i);
intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName());

View File

@ -1,4 +1,4 @@
package org.schabi.newpipe.player.refactor;
package org.schabi.newpipe.player.helper;
import android.content.Context;
import android.support.annotation.NonNull;

View File

@ -1,4 +1,4 @@
package org.schabi.newpipe.player.refactor;
package org.schabi.newpipe.player.helper;
import android.content.Context;

View File

@ -1,4 +1,4 @@
package org.schabi.newpipe.player.refactor;
package org.schabi.newpipe.player.helper;
import android.content.Context;
import android.net.wifi.WifiManager;

View File

@ -1,4 +1,4 @@
package org.schabi.newpipe.player.refactor;
package org.schabi.newpipe.player.helper;
import android.content.Context;
import android.content.SharedPreferences;
@ -80,6 +80,9 @@ public class PlayerHelper {
return 5000L;
}
public static boolean isUsingDSP(@NonNull final Context context) {
return true;
}
////////////////////////////////////////////////////////////////////////////
// Private helpers
////////////////////////////////////////////////////////////////////////////

View File

@ -1,5 +1,6 @@
package org.schabi.newpipe.playlist;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
@ -61,8 +62,8 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
public View view;
}
public PlayQueueAdapter(final PlayQueue playQueue) {
this.playQueueItemBuilder = new PlayQueueItemBuilder();
public PlayQueueAdapter(final Context context, final PlayQueue playQueue) {
this.playQueueItemBuilder = new PlayQueueItemBuilder(context);
this.playQueue = playQueue;
startReactor();
@ -94,9 +95,7 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
}
};
playQueue.getBroadcastReceiver()
.toObservable()
.subscribe(observer);
playQueue.getBroadcastReceiver().toObservable().subscribe(observer);
}
private void onPlayQueueChanged(final PlayQueueEvent message) {

View File

@ -1,11 +1,15 @@
package org.schabi.newpipe.playlist;
import android.content.Context;
import android.graphics.Bitmap;
import android.text.TextUtils;
import android.view.MotionEvent;
import android.view.View;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
import com.nostra13.universalimageloader.core.process.BitmapProcessor;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.Localization;
@ -15,6 +19,10 @@ public class PlayQueueItemBuilder {
private static final String TAG = PlayQueueItemBuilder.class.toString();
private final int thumbnailWidthPx;
private final int thumbnailHeightPx;
private final DisplayImageOptions imageOptions;
public interface OnSelectedListener {
void selected(PlayQueueItem item, View view);
void held(PlayQueueItem item, View view);
@ -23,7 +31,11 @@ public class PlayQueueItemBuilder {
private OnSelectedListener onItemClickListener;
public PlayQueueItemBuilder() {}
public PlayQueueItemBuilder(final Context context) {
thumbnailWidthPx = context.getResources().getDimensionPixelSize(R.dimen.play_queue_thumbnail_width);
thumbnailHeightPx = context.getResources().getDimensionPixelSize(R.dimen.play_queue_thumbnail_height);
imageOptions = buildImageOptions(thumbnailWidthPx, thumbnailHeightPx);
}
public void setOnSelectedListener(OnSelectedListener listener) {
this.onItemClickListener = listener;
@ -39,7 +51,7 @@ public class PlayQueueItemBuilder {
holder.itemDurationView.setVisibility(View.GONE);
}
ImageLoader.getInstance().displayImage(item.getThumbnailUrl(), holder.itemThumbnailView, IMAGE_OPTIONS);
ImageLoader.getInstance().displayImage(item.getThumbnailUrl(), holder.itemThumbnailView, imageOptions);
holder.itemRoot.setOnClickListener(new View.OnClickListener() {
@Override
@ -78,11 +90,23 @@ public class PlayQueueItemBuilder {
};
}
private static final DisplayImageOptions IMAGE_OPTIONS =
new DisplayImageOptions.Builder()
.cacheInMemory(true)
.showImageOnFail(R.drawable.dummy_thumbnail)
.showImageForEmptyUri(R.drawable.dummy_thumbnail)
.showImageOnLoading(R.drawable.dummy_thumbnail)
.build();
private DisplayImageOptions buildImageOptions(final int widthPx, final int heightPx) {
final BitmapProcessor bitmapProcessor = new BitmapProcessor() {
@Override
public Bitmap process(Bitmap bitmap) {
final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, widthPx, heightPx, false);
bitmap.recycle();
return resizedBitmap;
}
};
return new DisplayImageOptions.Builder()
.showImageOnFail(R.drawable.dummy_thumbnail)
.showImageForEmptyUri(R.drawable.dummy_thumbnail)
.showImageOnLoading(R.drawable.dummy_thumbnail)
.bitmapConfig(Bitmap.Config.RGB_565) // Users won't be able to see much anyways
.preProcessor(bitmapProcessor)
.imageScaleType(ImageScaleType.EXACTLY)
.build();
}
}

View File

@ -40,13 +40,13 @@
tools:listitem="@layout/play_queue_item"/>
<RelativeLayout
android:id="@+id/control_pane"
android:layout_width="200dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_above="@id/progress_bar"
android:layout_below="@id/appbar"
android:id="@+id/control_pane">
android:layout_below="@id/appbar">
<LinearLayout
android:id="@+id/metadata"
@ -114,14 +114,14 @@
<ImageButton
android:id="@+id/control_backward"
android:layout_width="40dp"
android:layout_height="match_parent"
android:layout_height="40dp"
android:layout_centerVertical="true"
android:layout_marginLeft="5dp"
android:layout_toLeftOf="@+id/control_play_pause"
android:clickable="true"
android:focusable="true"
android:padding="2dp"
android:scaleType="fitCenter"
android:tint="?attr/colorAccent"
android:src="@drawable/exo_controls_previous"
android:background="?attr/selectableItemBackgroundBorderless"
tools:ignore="ContentDescription"/>
@ -129,17 +129,17 @@
<ImageButton
android:id="@+id/control_play_pause"
android:layout_width="50dp"
android:layout_height="match_parent"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:layout_centerInParent="true"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="2dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitCenter"
android:tint="?attr/colorAccent"
android:src="@drawable/ic_pause_white"
tools:ignore="ContentDescription"/>
@ -147,13 +147,14 @@
android:id="@+id/control_progress_bar"
style="?android:attr/progressBarStyleLargeInverse"
android:layout_width="50dp"
android:layout_height="match_parent"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:layout_centerInParent="true"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:background="#00000000"
android:tint="?attr/colorAccent"
android:padding="2dp"
android:clickable="false"
android:scaleType="fitCenter"
@ -163,15 +164,15 @@
<ImageButton
android:id="@+id/control_forward"
android:layout_width="40dp"
android:layout_height="match_parent"
android:layout_height="40dp"
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
android:layout_toRightOf="@+id/control_play_pause"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:padding="2dp"
android:scaleType="fitCenter"
android:tint="?attr/colorAccent"
android:src="@drawable/exo_controls_next"
tools:ignore="ContentDescription"/>
</RelativeLayout>
@ -189,22 +190,22 @@
android:id="@+id/control_playback_speed"
android:layout_width="wrap_content"
android:layout_height="35dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/control_repeat"
android:gravity="center"
android:minWidth="50dp"
android:text="1x"
android:textColor="@android:color/white"
android:textColor="?attr/colorAccent"
android:textStyle="bold"
android:background="?attr/selectableItemBackground"
tools:ignore="HardcodedText,RtlHardcoded"/>
<ImageButton
android:id="@+id/control_repeat"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_toLeftOf="@+id/anchor"
android:layout_centerVertical="true"
android:layout_marginLeft="5dp"
@ -212,6 +213,7 @@
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
android:src="@drawable/ic_repeat_white"
tools:ignore="ContentDescription"/>
@ -222,8 +224,8 @@
<ImageButton
android:id="@+id/control_shuffle"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_toRightOf="@+id/anchor"
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
@ -231,6 +233,7 @@
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
android:src="@drawable/ic_shuffle_white_24dp"
tools:ignore="ContentDescription"/>
@ -238,14 +241,14 @@
android:id="@+id/control_playback_pitch"
android:layout_width="wrap_content"
android:layout_height="35dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/control_shuffle"
android:gravity="center"
android:minWidth="50dp"
android:text="100%"
android:textColor="@android:color/white"
android:textColor="?attr/colorAccent"
android:textStyle="bold"
android:background="?attr/selectableItemBackground"
tools:ignore="HardcodedText,RtlHardcoded"/>

View File

@ -41,11 +41,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/center"
android:layout_above="@+id/progress_bar">
android:layout_above="@+id/playback_controls">
<LinearLayout
android:id="@+id/metadata"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp"
@ -59,8 +59,11 @@
style="@android:style/TextAppearance.StatusBar.EventContent.Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="3"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:singleLine="true"
android:textSize="14sp"
android:textColor="?attr/colorAccent"
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis nec aliquam augue, eget cursus est. Ut id tristique enim, ut scelerisque tellus. Sed ultricies ipsum non mauris ultricies, commodo malesuada velit porta."
@ -71,8 +74,11 @@
style="@android:style/TextAppearance.StatusBar.EventContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:singleLine="true"
android:textSize="12sp"
tools:text="Duis posuere arcu condimentum lobortis mattis."/>
</LinearLayout>
@ -100,11 +106,12 @@
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/playback_controls"
android:layout_alignParentBottom="true"
android:gravity="center"
android:orientation="horizontal"
android:paddingLeft="16dp"
android:paddingRight="16dp">
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:background="@drawable/player_controls_bg">
<TextView
android:id="@+id/current_time"
@ -124,8 +131,10 @@
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:paddingBottom="4dp"
android:paddingTop="8dp"
android:paddingTop="6dp"
tools:progress="25"
tools:secondaryProgress="50"/>
@ -143,72 +152,71 @@
<RelativeLayout
android:id="@+id/playback_controls"
android:layout_width="match_parent"
android:layout_height="60dp"
android:paddingTop="10dp"
android:layout_alignParentBottom="true"
android:layout_height="wrap_content"
android:layout_above="@+id/progress_bar"
android:orientation="horizontal"
android:background="@drawable/player_controls_bg"
tools:ignore="RtlHardcoded">
<TextView
android:id="@+id/control_playback_speed"
android:layout_width="wrap_content"
android:layout_height="35dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/control_repeat"
android:gravity="center"
android:minWidth="50dp"
android:text="1x"
android:textColor="@android:color/white"
android:textColor="?attr/colorAccent"
android:textStyle="bold"
android:background="?attr/selectableItemBackground"
tools:ignore="HardcodedText,RtlHardcoded"/>
<ImageButton
android:id="@+id/control_repeat"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_toLeftOf="@+id/control_backward"
android:layout_centerVertical="true"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
android:src="@drawable/ic_repeat_white"
tools:ignore="ContentDescription"/>
<ImageButton
android:id="@+id/control_backward"
android:layout_width="40dp"
android:layout_height="match_parent"
android:layout_height="40dp"
android:layout_centerVertical="true"
android:layout_marginLeft="5dp"
android:layout_toLeftOf="@+id/control_play_pause"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:padding="2dp"
android:scaleType="fitCenter"
android:tint="?attr/colorAccent"
android:src="@drawable/exo_controls_previous"
tools:ignore="ContentDescription"/>
<ImageButton
android:id="@+id/control_play_pause"
android:layout_width="50dp"
android:layout_height="match_parent"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:layout_centerInParent="true"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="2dp"
android:clickable="true"
android:focusable="true"
android:scaleType="fitCenter"
android:tint="?attr/colorAccent"
android:src="@drawable/ic_pause_white"
tools:ignore="ContentDescription"/>
@ -216,45 +224,49 @@
android:id="@+id/control_progress_bar"
style="?android:attr/progressBarStyleLargeInverse"
android:layout_width="50dp"
android:layout_height="match_parent"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:layout_centerInParent="true"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:background="#00000000"
android:tint="?attr/colorAccent"
android:padding="2dp"
android:clickable="false"
android:scaleType="fitCenter"
android:indeterminate="true"
android:visibility="invisible"/>
android:visibility="invisible"
tools:visibility="visible"/>
<ImageButton
android:id="@+id/control_forward"
android:layout_width="40dp"
android:layout_height="match_parent"
android:layout_height="40dp"
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
android:layout_toRightOf="@+id/control_play_pause"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:padding="2dp"
android:scaleType="fitCenter"
android:tint="?attr/colorAccent"
android:src="@drawable/exo_controls_next"
tools:ignore="ContentDescription"/>
<ImageButton
android:id="@+id/control_shuffle"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_toRightOf="@+id/control_forward"
android:layout_centerVertical="true"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
android:src="@drawable/ic_shuffle_white_24dp"
tools:ignore="ContentDescription"/>
@ -262,14 +274,14 @@
android:id="@+id/control_playback_pitch"
android:layout_width="wrap_content"
android:layout_height="35dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/control_shuffle"
android:gravity="center"
android:minWidth="50dp"
android:text="100%"
android:textColor="@android:color/white"
android:textColor="?attr/colorAccent"
android:textStyle="bold"
android:background="?attr/selectableItemBackground"
tools:ignore="HardcodedText,RtlHardcoded"/>

View File

@ -12,8 +12,8 @@
<ImageView
android:id="@+id/itemThumbnailView"
android:layout_width="62dp"
android:layout_height="40dp"
android:layout_width="@dimen/play_queue_thumbnail_width"
android:layout_height="@dimen/play_queue_thumbnail_height"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"

View File

@ -20,7 +20,7 @@
android:maxLines="2"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/playlist_detail_title_text_size"
tools:text="Mix musics #23 title Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsum"/>
tools:text="Mix musics #23 title Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsum" />
<RelativeLayout
android:layout_width="match_parent"
@ -89,21 +89,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/play_control"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:layout_below="@+id/playlist_meta">
<Button
android:id="@+id/playlist_play_all_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_gravity="center_vertical|right"
android:layout_marginRight="2dp"
android:text="@string/play_all"
android:textSize="@dimen/channel_rss_title_size"
android:theme="@style/RedButton"
tools:ignore="RtlHardcoded"
tools:visibility="visible"/>
<Button
android:id="@+id/playlist_play_bg_button"
android:layout_width="wrap_content"
@ -114,21 +103,40 @@
android:layout_toStartOf="@+id/playlist_play_all_button"
android:text="@string/controls_background_title"
android:textSize="@dimen/channel_rss_title_size"
android:textColor="?attr/colorAccent"
android:theme="@style/RedButton"
android:drawableLeft="?attr/audio"
android:drawablePadding="4dp"
tools:ignore="RtlHardcoded"
tools:visibility="visible" />
<Button
android:id="@+id/playlist_play_all_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|right"
android:layout_marginRight="2dp"
android:layout_toLeftOf="@+id/playlist_play_popup_button"
android:layout_toStartOf="@+id/playlist_play_popup_button"
android:text="@string/play_all"
android:textSize="@dimen/channel_rss_title_size"
android:textColor="?attr/colorAccent"
android:theme="@style/RedButton"
tools:ignore="RtlHardcoded"
tools:visibility="visible"/>
<Button
android:id="@+id/playlist_play_popup_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|right"
android:layout_marginRight="2dp"
android:layout_toLeftOf="@+id/playlist_play_bg_button"
android:layout_toStartOf="@+id/playlist_play_bg_button"
android:layout_alignParentRight="true"
android:text="@string/controls_popup_title"
android:textSize="@dimen/channel_rss_title_size"
android:textColor="?attr/colorAccent"
android:theme="@style/RedButton"
android:drawableLeft="?attr/popup"
android:drawablePadding="4dp"
tools:ignore="RtlHardcoded"
tools:visibility="visible" />
</RelativeLayout>

View File

@ -67,6 +67,10 @@
<dimen name="playlist_detail_uploader_image_size">24dp</dimen>
<dimen name="playlist_detail_uploader_layout_height">28dp</dimen>
<!-- Play Queue View Dimensions -->
<dimen name="play_queue_thumbnail_width">62dp</dimen>
<dimen name="play_queue_thumbnail_height">40dp</dimen>
<!-- Kiosk view Dimensions-->
<dimen name="kiosk_title_text_size">30sp</dimen>
</resources>

View File

@ -136,9 +136,9 @@
<string name="could_not_get_stream">Could not get any stream</string>
<string name="could_not_load_image">Could not load image</string>
<string name="app_ui_crash">App/UI crashed</string>
<string name="player_video_failure">Failed to play this video</string>
<string name="player_audio_failure">Failed to play this audio</string>
<string name="player_unexpected_failure">Unexpected player error occurred</string>
<string name="player_stream_failure">Failed to play this stream</string>
<string name="player_unrecoverable_failure">Unrecoverable player error occurred</string>
<string name="player_recoverable_failure">Recovering from player error</string>
<!-- error activity -->
<string name="sorry_string">Sorry, that should not have happened.</string>