Skip to content

Commit

Permalink
Android: Lock screen almost completed! The only thing missing is the …
Browse files Browse the repository at this point in the history
…ability to change the song position. Everything else is done; the album art is updated, the play controls work and are updated, etc.The lock screen designed is inspired by iOS 7's lock screen when playing audio.

Related to issue #406.
  • Loading branch information
ycastonguay committed Aug 28, 2013
1 parent 612bbfa commit d47b5e2
Show file tree
Hide file tree
Showing 10 changed files with 683 additions and 473 deletions.
202 changes: 183 additions & 19 deletions MPfm/MPfm.Android/Classes/Activities/LockScreenActivity.cs
Expand Up @@ -16,63 +16,148 @@
// along with MPfm. If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Threading.Tasks;
using System.Timers;
using Android.App;
using Android.Content.PM;
using Android.Graphics;
using Android.Views;
using Android.OS;
using Android.Widget;
using Java.Lang;
using MPfm.Android.Classes.Cache;
using MPfm.MVP.Bootstrap;
using MPfm.MVP.Messages;
using MPfm.MVP.Navigation;
using MPfm.MVP.Models;
using MPfm.MVP.Services.Interfaces;
using MPfm.Sound.AudioFiles;
using TinyMessenger;

namespace MPfm.Android
{
[Activity(Label = "Sessions Lock Screen", ScreenOrientation = ScreenOrientation.Sensor, Theme = "@style/MyAppTheme", ConfigurationChanges = ConfigChanges.KeyboardHidden | ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
[Activity(Label = "Lock Screen", NoHistory = true, ScreenOrientation = ScreenOrientation.Sensor, Theme = "@style/MyAppTheme", ConfigurationChanges = ConfigChanges.KeyboardHidden | ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
public class LockScreenActivity : BaseActivity
{
private ITinyMessengerHub _messengerHub;
private TextView _lblArtistName;
private TextView _lblAlbumTitle;
private TextView _lblSongTitle;
ITinyMessengerHub _messengerHub;
IPlayerService _playerService;
BitmapCache _bitmapCache;
TextView _lblArtistName;
TextView _lblAlbumTitle;
TextView _lblSongTitle;
TextView _lblPosition;
TextView _lblLength;
ImageButton _btnPrevious;
ImageButton _btnPlayPause;
ImageButton _btnNext;
Button _btnClose;
SeekBar _seekBar;
ImageView _imageAlbum;
Bitmap _bitmapAlbumArt;
string _previousAlbumArtKey;
Timer _timerSongPosition;
bool _isPositionChanging;

protected override void OnCreate(Bundle bundle)
{
Console.WriteLine("LockScreenActivity - OnCreate");
base.OnCreate(bundle);

// Create bitmap cache
int maxMemory = (int)(Runtime.GetRuntime().MaxMemory() / 1024);
int cacheSize = maxMemory / 16;
_bitmapCache = new BitmapCache(null, cacheSize, 800, 800);

// Create layout and get controls
SetContentView(Resource.Layout.LockScreen);
_lblArtistName = FindViewById<TextView>(Resource.Id.lockScreen_lblArtistName);
_lblAlbumTitle = FindViewById<TextView>(Resource.Id.lockScreen_lblAlbumTitle);
_lblSongTitle = FindViewById<TextView>(Resource.Id.lockScreen_lblSongTitle);
_lblPosition = FindViewById<TextView>(Resource.Id.lockScreen_lblPosition);
_lblLength = FindViewById<TextView>(Resource.Id.lockScreen_lblLength);
_btnPrevious = FindViewById<ImageButton>(Resource.Id.lockScreen_btnPrevious);
_btnPlayPause = FindViewById<ImageButton>(Resource.Id.lockScreen_btnPlayPause);
_btnNext = FindViewById<ImageButton>(Resource.Id.lockScreen_btnNext);
_btnClose = FindViewById<Button>(Resource.Id.lockScreen_btnClose);
_imageAlbum = FindViewById<ImageView>(Resource.Id.lockScreen_imageAlbum);
_seekBar = FindViewById<SeekBar>(Resource.Id.lockScreen_seekBar);
_seekBar.StartTrackingTouch += SeekBarOnStartTrackingTouch;
_seekBar.StopTrackingTouch += SeekBarOnStopTrackingTouch;
_seekBar.ProgressChanged += SeekBarOnProgressChanged;

_playerService = Bootstrapper.GetContainer().Resolve<IPlayerService>();
_messengerHub = Bootstrapper.GetContainer().Resolve<ITinyMessengerHub>();
_messengerHub.Subscribe<PlayerPlaylistIndexChangedMessage>((message) =>
{
Console.WriteLine("LockScreenActivity - PlayerPlaylistIndexChangedMessage");
if (message.Data.AudioFileStarted != null)
{
if (_lblArtistName != null)
_messengerHub.Subscribe<PlayerPlaylistIndexChangedMessage>((message) => {
RunOnUiThread(() => {
//Console.WriteLine("LockScreenActivity - PlayerPlaylistIndexChangedMessage");
if (message.Data.AudioFileStarted != null)
{
_lblArtistName.Text = message.Data.AudioFileStarted.ArtistName;
_lblAlbumTitle.Text = message.Data.AudioFileStarted.AlbumTitle;
_lblSongTitle.Text = message.Data.AudioFileStarted.Title;
if (_lblArtistName != null)
{
//Console.WriteLine("LockScreenActivity - PlayerPlaylistIndexChangedMessage - Updating controls...");
_lblArtistName.Text = message.Data.AudioFileStarted.ArtistName;
_lblAlbumTitle.Text = message.Data.AudioFileStarted.AlbumTitle;
_lblSongTitle.Text = message.Data.AudioFileStarted.Title;
_lblLength.Text = message.Data.AudioFileStarted.Length;
}
}
}
GetAlbumArt(message.Data.AudioFileStarted);
});
});
_messengerHub.Subscribe<PlayerStatusMessage>((message) =>
{
Console.WriteLine("LockScreenActivity - PlayerStatusMessage - Status=" + message.Status.ToString());
RunOnUiThread(() => {
//Console.WriteLine("LockScreenActivity - PlayerStatusMessage - Status: " + message.Status.ToString());
var status = message.Status;
if (status == PlayerStatusType.Initialized ||
status == PlayerStatusType.Paused ||
status == PlayerStatusType.Stopped)
{
_btnPlayPause.SetImageResource(Resource.Drawable.player_play);
}
else
{
_btnPlayPause.SetImageResource(Resource.Drawable.player_pause);
}
});
});

_btnClose.Click += (sender, args) => Finish();
_btnPrevious.Click += (sender, args) => _messengerHub.PublishAsync<PlayerCommandMessage>(new PlayerCommandMessage(this, PlayerCommandMessageType.Previous));
_btnPlayPause.Click += (sender, args) => _messengerHub.PublishAsync<PlayerCommandMessage>(new PlayerCommandMessage(this, PlayerCommandMessageType.PlayPause));
_btnNext.Click += (sender, args) => _messengerHub.PublishAsync<PlayerCommandMessage>(new PlayerCommandMessage(this, PlayerCommandMessageType.Next));

_timerSongPosition = new Timer();
_timerSongPosition.Interval = 100;
_timerSongPosition.Elapsed += (sender, args) => {
if (_isPositionChanging)
return;
RunOnUiThread(() => {
try
{
var position = _playerService.GetPosition();
//Console.WriteLine("LockScreenActivity - timerSongPosition - position: {0}", position);
_lblPosition.Text = position.Position;
float percentage = ((float)position.PositionBytes / (float)_playerService.CurrentPlaylistItem.LengthBytes) * 10000f;
_seekBar.Progress = (int)percentage;
}
catch
{
// Just ignore exception. It's not really worth it to start/stop the timer when the player is playing.
_lblPosition.Text = "0:00.000";
_seekBar.Progress = 0;
}
});
};
_timerSongPosition.Start();
}

public override void OnAttachedToWindow()
{
Console.WriteLine("LockScreenActivity - OnAttachedToWindow");
var window = this.Window;
window.AddFlags(WindowManagerFlags.ShowWhenLocked);
//window.AddFlags(WindowManagerFlags.TurnScreenOn | WindowManagerFlags.ShowWhenLocked |
// WindowManagerFlags.KeepScreenOn | WindowManagerFlags.DismissKeyguard);
window.SetWindowAnimations(0);
}

protected override void OnStart()
Expand All @@ -97,6 +182,27 @@ protected override void OnResume()
{
Console.WriteLine("LockScreenActivity - OnResume");
base.OnResume();

if (!_playerService.IsPlaying || _playerService.CurrentPlaylistItem == null)
return;

_lblArtistName.Text = _playerService.CurrentPlaylistItem.AudioFile.ArtistName;
_lblAlbumTitle.Text = _playerService.CurrentPlaylistItem.AudioFile.AlbumTitle;
_lblSongTitle.Text = _playerService.CurrentPlaylistItem.AudioFile.Title;
_lblLength.Text = _playerService.CurrentPlaylistItem.AudioFile.Length;
GetAlbumArt(_playerService.CurrentPlaylistItem.AudioFile);

var status = _playerService.Status;
if (status == PlayerStatusType.Initialized ||
status == PlayerStatusType.Paused ||
status == PlayerStatusType.Stopped)
{
_btnPlayPause.SetImageResource(Resource.Drawable.player_play);
}
else
{
_btnPlayPause.SetImageResource(Resource.Drawable.player_pause);
}
}

protected override void OnStop()
Expand All @@ -110,5 +216,63 @@ protected override void OnDestroy()
Console.WriteLine("LockScreenActivity - OnDestroy");
base.OnDestroy();
}

public override void OnBackPressed()
{
//base.OnBackPressed();
Console.WriteLine("LockScreenActivity - OnBackPressed");
Finish();
}

private void SeekBarOnStartTrackingTouch(object sender, SeekBar.StartTrackingTouchEventArgs e)
{
Console.WriteLine("SeekBarOnStartTrackingTouch");
_isPositionChanging = true;
}

private void SeekBarOnStopTrackingTouch(object sender, SeekBar.StopTrackingTouchEventArgs e)
{
Console.WriteLine("SeekBarOnStopTrackingTouch progress: {0}", _seekBar.Progress);
//OnPlayerSetPosition(_seekBar.Progress);
_isPositionChanging = false;
}

private void SeekBarOnProgressChanged(object sender, SeekBar.ProgressChangedEventArgs e)
{
Console.WriteLine("SeekBarOnProgressChanged");
//PlayerPositionEntity entity = OnPlayerRequestPosition((float)_seekBar.Progress / 100f);
//_lblPosition.Text = entity.Position;
}

private void GetAlbumArt(AudioFile audioFile)
{
_bitmapAlbumArt = null;
Task.Factory.StartNew(() =>
{
Console.WriteLine("LockScreenActivity - GetAlbumArt - audioFile.Path: {0}", audioFile.FilePath);
string key = audioFile.ArtistName + "_" + audioFile.AlbumTitle;
if (string.IsNullOrEmpty(_previousAlbumArtKey) || _previousAlbumArtKey.ToUpper() != key.ToUpper())
{
Console.WriteLine("LockScreenActivity - GetAlbumArt - key: {0} is different than tag {1} - Fetching album art...", key, _previousAlbumArtKey);
_previousAlbumArtKey = key;
byte[] bytesImage = AudioFile.ExtractImageByteArrayForAudioFile(audioFile.FilePath);
if (bytesImage.Length == 0)
{
Console.WriteLine("LockScreenActivity - GetAlbumArt - Setting album art to NULL!");
RunOnUiThread(() => _imageAlbum.SetImageBitmap(_bitmapAlbumArt));
}
else
{
Console.WriteLine("LockScreenActivity - GetAlbumArt - Getting album art in another thread...");
_bitmapCache.LoadBitmapFromByteArray(bytesImage, key, bitmap =>
{
Console.WriteLine("WidgetService - GetAlbumArt - RECEIVED ALBUM ART! SETTING ALBUM ART");
_bitmapAlbumArt = bitmap;
RunOnUiThread(() => _imageAlbum.SetImageBitmap(_bitmapAlbumArt));
});
}
}
});
}
}
}
20 changes: 0 additions & 20 deletions MPfm/MPfm.Android/Classes/Activities/MainActivity.cs
Expand Up @@ -84,16 +84,6 @@ public class MainActivity : BaseActivity, IMobileOptionsMenuView, View.IOnTouchL

public BitmapCache BitmapCache { get; private set; }

public override void OnAttachedToWindow()
{
//base.OnAttachedToWindow();

Console.WriteLine("LockScreenActivity - OnAttachedToWindow");
var window = this.Window;
window.AddFlags(WindowManagerFlags.TurnScreenOn | WindowManagerFlags.ShowWhenLocked |
WindowManagerFlags.KeepScreenOn | WindowManagerFlags.DismissKeyguard);
}

protected override void OnCreate(Bundle bundle)
{
Console.WriteLine("MainActivity - OnCreate");
Expand Down Expand Up @@ -209,19 +199,9 @@ protected override void OnCreate(Bundle bundle)
// }
//#endif

//var intent = new Intent(this, typeof(LockScreenActivity));
//intent.AddFlags(ActivityFlags.ClearTop | ActivityFlags.SingleTop);
//this.StartActivity(intent);

var window = this.Window;
//window.AddFlags(WindowManagerFlags.TurnScreenOn | WindowManagerFlags.ShowWhenLocked |
// WindowManagerFlags.KeepScreenOn | WindowManagerFlags.DismissKeyguard);
window.AddFlags(WindowManagerFlags.ShowWhenLocked);
//KeyguardManager myKeyGuard = (KeyguardManager)getSystemService(Context.KEYGUARD_SERVICE); myLock = myKeyGuard.newKeyguardLock(); myLock.disableKeyguard();
//KeyguardManager keyguardManager = (KeyguardManager) GetSystemService(KeyguardService);



Console.WriteLine("MainActivity - OnCreate - Starting navigation manager...");
_navigationManager = (AndroidNavigationManager) Bootstrapper.GetContainer().Resolve<MobileNavigationManager>();
_navigationManager.MainActivity = this; // TODO: Is this OK? Shouldn't the reference be cleared when MainActivity is destroyed? Can lead to memory leaks.
Expand Down
5 changes: 3 additions & 2 deletions MPfm/MPfm.Android/Classes/Receivers/LockReceiver.cs
Expand Up @@ -46,8 +46,9 @@ public override void OnReceive(Context context, Intent intent)
if (intent.Action == Intent.ActionScreenOff)
{
Intent newIntent = new Intent();
newIntent.SetClass(context, typeof(LockScreenActivity));
newIntent.SetFlags(ActivityFlags.NewTask | ActivityFlags.ClearTop | ActivityFlags.SingleTop);
newIntent.SetClass(context, typeof(LockScreenActivity));
// New task is required when starting an activity outside an activity.
newIntent.SetFlags(ActivityFlags.NewTask | ActivityFlags.ClearTop | ActivityFlags.SingleTop | ActivityFlags.NoAnimation);
context.StartActivity(newIntent);
}
}
Expand Down
13 changes: 6 additions & 7 deletions MPfm/MPfm.Android/Classes/Services/WidgetService.cs
Expand Up @@ -42,12 +42,12 @@ namespace org.sessionsapp.android
[Service(Label = "Sessions Widget Service")]//, Process = ":widgetprocess")]
public class WidgetService : Service
{
private ITinyMessengerHub _messengerHub;
private IPlayerService _playerService;
private int[] _widgetIds;
private BitmapCache _bitmapCache;
private string _previousAlbumArtKey;
private Notification _notification;
ITinyMessengerHub _messengerHub;
IPlayerService _playerService;
int[] _widgetIds;
BitmapCache _bitmapCache;
string _previousAlbumArtKey;
Notification _notification;
bool _isShutDowning;
AudioFile _audioFile;
PlayerStatusType _status;
Expand Down Expand Up @@ -217,7 +217,6 @@ private void GetAlbumArt(AudioFile audioFile)
}
}
});

}

public override IBinder OnBind(Intent intent)
Expand Down

0 comments on commit d47b5e2

Please sign in to comment.