The game starts from MainActivity class (that extends Activity) and then calls new GamePanel that extends SurfaceView, and there are many classes that update the game, so there is a class (GameplayScene) that checks the touch and check what happened to the game (if the user win or lose), so there is an update method that check if the player wins or lose and if the player win or lose then I want to start a new activity that have a button to restart the game,
The issue is: After starting the new activity (by using Intent from a class that does not extends anything) the app does not respond (but it displays the new activity correctly)
the class that starts the new activity from () :
public class GameplayScene implements Scene {
public GameplayScene() {
....
#Override
public void recieveTouch(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (!youWin && !gameOver && player.getRectangle().contains((int) event.getX(), (int) event.getY()))
movingPlayer = true;
forBegin = false;
if (gameOver && System.currentTimeMillis() - gameOverTime >= 2000) {
reset();
gameOver = false;
}
if (youWin && System.currentTimeMillis() - winTime >= 2000) {
reset();
youWin = false;
}
if(!youWin && !gameOver && !movingPlayer) {
if(!forBegin && System.currentTimeMillis() - fireTime >= 200) {
touchFire = true;
count = 1;
fireTime = System.currentTimeMillis();
}
}
break;
case MotionEvent.ACTION_MOVE:
if (!youWin && !gameOver && movingPlayer)
playerPoint.set((int) event.getX(), (int) event.getY());
break;
case MotionEvent.ACTION_UP:
movingPlayer = false;
touchFire = false;
break;
}
}
//#Override
public void draw(Canvas canvas) {
canvas.drawColor(Color.BLACK);
//Drawable d = getDrawable(getResources(), R.drawable.bgi);
//d.setBounds(0, 0,canvas.getWidth(), canvas.getHeight());
//d.draw(canvas);
//canvas.drawBitmap(bgi, null, new Rect(0, 0, canvas.getWidth(), canvas.getHeight()),new Paint());
player.draw(canvas);
obstacleManager.draw(canvas);
if(gameOver) {
Context contX = Constants.CONTEXT;
Intent intent = new Intent(contX , result.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("toWR",0);
contX.startActivity(intent);
}
if(youWin) {
Context contX = Constants.CONTEXT;
Intent intent = new Intent(contX , result.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("toWR",1);
contX.startActivity(intent);
}
}
}
Android manifest :
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme" >
<activity android:name=".main_menu"
android:screenOrientation="portrait" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".MainActivity"
android:screenOrientation="portrait" />
<SurfaceView android:name=".GamePanel"
android:screenOrientation="portrait" />
<activity android:name=".result"
android:screenOrientation="portrait" />
</application>
I'm assuming you have some kind of DrawingThread(...) that is delegating the draw(...) to this Scene. If that is the case, then you're probably aiming for 60 updates per second... which means this intent will try to launch 60 times per second. That's probably what is causing your app to seem like it isn't responding.
Instead of launching an intent from in here, change the state of your game (or reuse the gameOver state) and interrupt your game loop (drawing thread). Then, start your activity once outside of the game loop, but before your thread finishes.
Related
I am making a code for making phone calls. Everything successful except that I am unable to get the system time when TelephonyManager.CALL_STATE_OFFHOOK state from the onCallStateChanged(int state, String incomingNumber).
Similarly, I want to get the system time when TelephonyManager.CALL_STATE_IDLE state is arrived. Once I get both the values in the Mainactivity I can get the difference and based its value I can start some other activity.
For example, if the time difference between TelephonyManager.CALL_STATE_IDLE state and TelephonyManager.CALL_STATE_IDLE state is less than 30 second, then I can assume that the call is not attended and I can start another call(or to another number). I am able to do all these inside the onCallStateChanged Method of PhoneStateListener class. But unable to pass this values to Mainactivity either by variables or Methods etc. This is the code for invoking call and it is working fine
Intent myIntentCall = new Intent(Intent.ACTION_CALL, Uri.parse("tel:0123456789"));
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)
!= PackageManager.PERMISSION_GRANTED) {
requestPermissions();
}
else
{
startActivity(myIntentCall);
}
private void requestPermissions () {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, 1);
}
From the below piece of code, I expect the updated value of _seconds to the Mainactivity class by accessing PhoneReceiver._seconds statement. But always it remain 0. But I get the correct value for _seconds in the PhoneReceiver class. The code is given below
public class PhoneReceiver extends PhoneStateListener {
Context context;
static boolean _callStarted = false;
long _callStartTime;
long _callEndTime;
long _callDuration;
long _minutes;
static long _seconds;
public PhoneReceiver(Context context) {
this.context = context;
}
#Override
public void onCallStateChanged(int state, String incomingNumber) {
if ((state == TelephonyManager.CALL_STATE_OFFHOOK) && !_callStarted) {
_callStarted = !_callStarted;
_callStartTime = new Date().getTime();
Toast.makeText(context, "Stage 1: " + "Off Hook -> Boolean: "+_callStarted, Toast.LENGTH_LONG).show();
}
if ((state == TelephonyManager.CALL_STATE_IDLE) && _callStarted)
{
_callEndTime = new Date().getTime();
_callDuration = _callEndTime - _callStartTime;
_minutes = (_callDuration / 1000) / 60;
_seconds = (_callDuration / 1000) % 60;
Toast.makeText(context, "Stage 2: " + "IDLE State->Boolean: "+_callStarted, Toast.LENGTH_LONG).show();
}
}
}
Android Manifest.xml is given below
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.callerApppackage.callerapp">
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- put this here so that even if the app is not running,
your app can be woken up if there is a change in phone
state -->
<receiver android:name=".PhoneStateReceiver">
<intent-filter>
<action
android:name=
"android.intent.action.PHONE_STATE" />
</intent-filter>
</receiver>
</application>
PhonestateReciver Class is given below
public class PhoneStateReceiver extends BroadcastReceiver {
TelephonyManager manager;
PhoneReceiver myPhoneStateListener;
static boolean alreadyListening = false;
#Override
public void onReceive(Context context, Intent intent) {
myPhoneStateListener = new PhoneReceiver(context);
manager = ((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE));
//---do not add the listener more than once---
if (!alreadyListening) {
manager.listen(myPhoneStateListener,
PhoneStateListener.LISTEN_CALL_STATE);
alreadyListening = true;
}
}
}
I hope I made it clear Thanking you all in advance for earlier reply.
I have an application that starts an Intent after the boot that works from Android 6 to Android 9 API level 28.
But this code does not work on Android 10 API level 29, Broadcast simply does not receive any events and does not run onReceive on MyClassBroadcastReceiver after the boot. Is there any extra permission on Android 10 or configuration that needs to be done?
Dry part of the example: Manifest:
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.softniels.autostartonboot">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name="com.softniels.autostartonboot.ForegroundService"
android:label="My Service">
<intent-filter>
<action android:name="com.softniels.autostartonboot.ForegroundService" />
</intent-filter>
</service>
<receiver
android:name=".StartMyServiceAtBootReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
</intent-filter>
</receiver>
</application>
Here the part that doesn't run on Android 10.
public class StartMyServiceAtBootReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
Log.i("onReceive", "call onReceive ACTION_BOOT_COMPLETED");
Intent i = new Intent(context, MainActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
}
}
}
I know that this may be old but I have faced the same problem and according to this:
https://developer.android.com/guide/components/activities/background-starts
The easiest solution I came up with was simply adding
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
And setting up the receiver:
<receiver
android:name=".BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
To the manifest.
Receiver code:
#Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
// Intent n = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
// n.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
// Intent.FLAG_ACTIVITY_CLEAR_TASK);
// context.startActivity(n);
Intent myIntent = new Intent(context, MainActivity.class);
myIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(myIntent);
}
}
Both options work. The only downside I see is that it takes rather a while for app to load (can be up to 10 seconds from my testings)
Leaving this here for other people if they encounter this as well.
This only applies to android 10 and up. There is a need to request "Display over other apps" permission
This requires drawing overlay, which can be done with:
if (!Settings.canDrawOverlays(getApplicationContext())) {
Intent myIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
Uri uri = Uri.fromParts("package", getPackageName(), null);
myIntent.setData(uri);
startActivityForResult(myIntent, REQUEST_OVERLAY_PERMISSIONS);
return;
}
Guess I found a 'solution' for me.
public class StartMyServiceAtBootReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
Log.e(TAG, "launching from special > API 28 (" + Build.VERSION.SDK_INT + ")"); // You have to schedule a Service
JobServiceScheduler jobServiceScheduler = new JobServiceScheduler(context);
boolean result = jobServiceScheduler.scheduleMainService(20L); // Time you will wait to launch
} else {
Log.e(TAG, "launching from normal < API 29"); // You can still launch an Activity
try {
Intent intentMain = new Intent(context, YourActivity.class);
intentMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT < 28) {
context.startService(intentMain);
} else {
context.startForegroundService(intentMain);
}
} catch (ActivityNotFoundException ex) {
Log.e(TAG, "ActivityNotFoundException" + ex.getLocalizedMessage());
}
}
}
boolean scheduleMainService(Long segundos) {
ComponentName serviceComponent = new ComponentName(context, YourService.class);
JobInfo.Builder builder = getCommonBuilder(serviceComponent, YOUR_SERVICE_JOB_ID);
builder.setMinimumLatency(TimeUnit.SECONDS.toMillis(segundos / 2)); // wait at least
builder.setOverrideDeadline(TimeUnit.SECONDS.toMillis(segundos)); // maximum delay
PersistableBundle extras = new PersistableBundle();
extras.putLong("time", segundos);
builder.setExtras(extras);
JobScheduler jobScheduler = getJobScheduler(context);
if (jobScheduler != null) {
jobScheduler.schedule(builder.build());
return true;
} else {
return false;
}
}
context.startActivity() is not launching, I solved it the following way:
private void restartApp( Context mContext) {
try {
long restartTime = 1000*5;
Intent intents = mContext.getPackageManager().getLaunchIntentForPackage(mContext.getPackageName());
PendingIntent restartIntent = PendingIntent.getActivity(mContext, 0, intents, PendingIntent.FLAG_ONE_SHOT);
AlarmManager mgr = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mgr.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + restartTime, restartIntent);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mgr.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + restartTime, restartIntent);
}
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}
I solved it with this permission in the manifest:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
And in the main activity :
if (!Settings.canDrawOverlays(getApplicationContext())) {
startActivity(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION));
}
The correct import for Settings is:android.provider
The first time the app boots the permission will be prompted for controlling which apps can draw on top of other apps, the next device will start the application will boot up using the typical broadcast receiver.
Here is the doc
I am creating an sleep timer app that gradually lowers the system volume as time goes by. If the user sets the time to 30 minutes then the volume will decrease at 15 minutes then 7.5 minutes, etc.
I currently have the volume decay being sent to a JobService which works just fine when it's up on my phone screen, but once I lock my phone and it's left in the background it will work for a few minutes tops. I have also tried using Service and IntentService, both yielding similar results.
I wonder if these threads think they are finished even though they are not, since I am using a handle.postDelayed which calls itself.
Why doesn't it run in the background for more than a few minutes? Do I need to have a checker inside of onStartJob to see if startVolumeDecrement is finished?
Below is relevant code (full code at very bottom of post):
MainActivity.java
package com.example.dreamquiet;
//imports
/.../
public class MainActivity extends AppCompatActivity {
//Methods:
#Override
protected void onCreate(Bundle savedInstanceState) { //method that does it all!
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startDecay()
}
public void startDecay(){
//this is used to pass the time to the job.
PersistableBundle bundle = new PersistableBundle();
bundle.putInt("totsleep", totalSleepTime);
JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo jobInfo = new JobInfo.Builder(11, new ComponentName(this, StartDecayJob.class))
// only add if network access is required
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setExtras(bundle) //this is used to pass the time to the job.
.build();
jobScheduler.schedule(jobInfo);
}
public void stopDecay(){
JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheduler.cancelAll();
}
StartDecayJob.java
package com.example.dreamquiet;
//bunch of imports
/..../
public class StartDecayJob extends JobService {
protected int counter;
protected int totalSleepTime;
protected boolean isDone;
final Handler handleDecay = new Handler();
#Override
public boolean onStartJob(JobParameters jobParameters) {
totalSleepTime = jobParameters.getExtras().getInt("totsleep");
startVolumeDecrement();
return true;
}
#Override
public boolean onStopJob(JobParameters jobParameters) {
handleDecay.removeCallbacksAndMessages(null); //wipe out the current startVolumeDecrement
super.onDestroy();
stopSelf();
Toast.makeText(this, "Goodnight :)", Toast.LENGTH_LONG).show();
// return true to restart the job
return false;
}
//the method that lowers the volume over time.
protected void startVolumeDecrement() {
//get the audio
final AudioManager phoneVolume = (AudioManager) getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
int currentVolume = phoneVolume.getStreamVolume(AudioManager.STREAM_MUSIC);
//get the vector. Yes it's ugly. enjoy it.
Vector decayVector = new Vector();
decayVector = populateDecay(totalSleepTime, decayVector, currentVolume);
final Vector decay = decayVector;
counter = 0; //Reset the counter --> safe :P
final Runnable volDecay = new Runnable(){
public void run(){
if(counter >= decay.size()-1 || phoneVolume.getStreamVolume(AudioManager.STREAM_MUSIC) == 0){
//if current volume isn't muted then MUTE IT!
if(phoneVolume.getStreamVolume(AudioManager.STREAM_MUSIC) > 0){
counter--;
handleDecay.postDelayed(this, ((int)decay.get(decay.size()-1)));
}
//If using spotify, it will pause it! :)
Intent pauseSpotify = new Intent("com.spotify.mobile.android.ui.widget.PLAY");
pauseSpotify.setPackage("com.spotify.music");
sendBroadcast(pauseSpotify);
return; //return is redundant but why not...
}
//decrease volume then recall function
phoneVolume.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_LOWER, AudioManager.FLAG_SHOW_UI);
handleDecay.postDelayed(this, ((int) decay.get(counter++)));
}
};
handleDecay.postDelayed(volDecay, ((int) decay.get(counter++))); //start the initial counter
}
}
My manifest file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.dreamquiet" >
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"></uses-permission> <!--preventitive measures-->
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme" >
<activity android:name=".MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<meta-data
android:name="preloaded_fonts"
android:resource="#array/preloaded_fonts" />
<!-- intent defined in this service tag below -->
<service android:name=".StartDecayJob"
android:label="decay"
android:permission="android.permission.BIND_JOB_SERVICE"/> <!--Gotta see if process will work -->
</application>
</manifest>
Full code here
Because when you lock your phone os will pause the running service if many services running in different application and same issue i face when i was build quiz app.So solution is you can use WAKE LOCK it solved the problem.
In activity :
private var wakeLock: PowerManager.WakeLock? = null
To start the wake lock :
var powerManager = getSystemService(POWER_SERVICE) as PowerManager
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"ExampleApp:Wakelock Started")
wakeLock?.acquire()
Most important step to release the wake lock :
if (wakeLock?.isHeld!!) {
L.e("ExampleApp", "Wakelock Released")
wakeLock?.release();
}
At last add permission in manifest :
<uses-permission android:name="android.permission.WAKE_LOCK" />
I have a simple music player app (source) which has had playback issues in Lollipop when using headphones. Music will play normally for anywhere from 30 seconds to 5 minutes, then will pause for ~2-4 seconds, then resume.
The behavior seems to generally occur while the screen is off, but acquiring a CPU wakelock didn't help.
The frequency of the pauses seems to accelerate over time. At first it's once per hour, but then the time between pauses decreases by about half each time, until it's pausing almost every minute.
I've observed this behavior with iTunes encoded aac files, others have observed it with mp3s.
This has only been observed while playing over wired headphones. I have never experienced this behavior on a Bluetooth headset.
What could be causing this? It seems like a process priority issue, but I don't know how to address that kind of problem.
I haven't experienced this on Android 4.x.
Here's the Github ticket for this issue.
Here are some relevant bits of source code:
Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.smithdtyler.prettygoodmusicplayer"
android:versionCode="65"
android:versionName="3.2.14" >
<uses-sdk
android:minSdkVersion="16"
android:targetSdkVersion="19" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:allowBackup="true"
android:icon="#drawable/ic_pgmp_launcher"
android:label="#string/app_name"
android:theme="#style/AppBaseTheme" >
<!-- Set the artist list to launch mode single task to prevent multiple instances -->
<!-- This fixes an error where exiting the application just brings up another instance -->
<!-- See https://developer.android.com/guide/topics/manifest/activity-element.html#lmode -->
<activity
android:name="com.smithdtyler.prettygoodmusicplayer.ArtistList"
android:label="#string/app_name"
android:launchMode="singleTask" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.CATEGORY_APP_MUSIC " />
</intent-filter>
</activity>
<activity
android:name="com.smithdtyler.prettygoodmusicplayer.SettingsActivity"
android:label="#string/title_activity_settings" >
</activity>
<activity
android:name="com.smithdtyler.prettygoodmusicplayer.AlbumList"
android:label="#string/title_activity_album_list" >
</activity>
<activity
android:name="com.smithdtyler.prettygoodmusicplayer.SongList"
android:label="#string/title_activity_song_list" >
</activity>
<activity
android:name="com.smithdtyler.prettygoodmusicplayer.NowPlaying"
android:exported="true"
android:label="#string/title_activity_now_playing" >
</activity>
<!--
The service has android:exported="true" because that's needed for
control from the notification. Not sure why it causes a warning...
-->
<service
android:name="com.smithdtyler.prettygoodmusicplayer.MusicPlaybackService"
android:exported="true"
android:icon="#drawable/ic_pgmp_launcher" >
</service>
<receiver
android:name="com.smithdtyler.prettygoodmusicplayer.MusicBroadcastReceiver"
android:enabled="true" >
<intent-filter android:priority="2147483647" >
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
</application>
</manifest>
MusicPlaybackService.onCreate()
#Override
public synchronized void onCreate() {
Log.i(TAG, "Music Playback Service Created!");
isRunning = true;
sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
powerManager =(PowerManager) getSystemService(POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"PGMPWakeLock");
random = new Random();
mp = new MediaPlayer();
mp.setOnCompletionListener(new OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
Log.i(TAG, "Song complete");
next();
}
});
// https://developer.android.com/training/managing-audio/audio-focus.html
audioFocusListener = new PrettyGoodAudioFocusChangeListener();
// Get permission to play audio
am = (AudioManager) getBaseContext().getSystemService(
Context.AUDIO_SERVICE);
HandlerThread thread = new HandlerThread("ServiceStartArguments");
thread.start();
// Get the HandlerThread's Looper and use it for our Handler
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
// https://stackoverflow.com/questions/19474116/the-constructor-notification-is-deprecated
// https://stackoverflow.com/questions/6406730/updating-an-ongoing-notification-quietly/15538209#15538209
Intent resultIntent = new Intent(this, NowPlaying.class);
resultIntent.putExtra("From_Notification", true);
resultIntent.putExtra(AlbumList.ALBUM_NAME, album);
resultIntent.putExtra(ArtistList.ARTIST_NAME, artist);
resultIntent.putExtra(ArtistList.ARTIST_ABS_PATH_NAME, artistAbsPath);
// Use the FLAG_ACTIVITY_CLEAR_TOP to prevent launching a second
// NowPlaying if one already exists.
resultIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
resultIntent, 0);
Builder builder = new NotificationCompat.Builder(
this.getApplicationContext());
String contentText = getResources().getString(R.string.ticker_text);
if (songFile != null) {
contentText = Utils.getPrettySongName(songFile);
}
Notification notification = builder
.setContentText(contentText)
.setSmallIcon(R.drawable.ic_pgmp_launcher)
.setWhen(System.currentTimeMillis())
.setContentIntent(pendingIntent)
.setContentTitle(
getResources().getString(R.string.notification_title))
.build();
startForeground(uniqueid, notification);
timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
onTimerTick();
}
}, 0, 500L);
Log.i(TAG, "Registering event receiver");
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
// Apparently audio registration is persistent across lots of things...
// restarts, installs, etc.
mAudioManager.registerMediaButtonEventReceiver(cn);
// I tried to register this in the manifest, but it doesn't seen to
// accept it, so I'll do it this way.
getApplicationContext().registerReceiver(receiver, filter);
headphoneReceiver = new HeadphoneBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.intent.action.HEADSET_PLUG");
registerReceiver(headphoneReceiver, filter);
}
MusicPlaybackService.startPlayingFile()
private synchronized void startPlayingFile(int songProgress) {
// Have we loaded a file yet?
if (mp.getDuration() > 0) {
pause();
mp.stop();
mp.reset();
}
// open the file, pass it into the mp
try {
fis = new FileInputStream(songFile);
mp.setDataSource(fis.getFD());
mp.prepare();
if(songProgress > 0){
mp.seekTo(songProgress);
}
wakeLock.acquire();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
MusicPlaybackService Timer Task
private void onTimerTick() {
long currentTime = System.currentTimeMillis();
if (pauseTime < currentTime) {
pause();
}
updateResumePosition();
sendUpdateToClients();
}
private void updateResumePosition(){
long currentTime = System.currentTimeMillis();
if(currentTime - 10000 > lastResumeUpdateTime){
if(mp != null && songFile != null && mp.isPlaying()){
int pos = mp.getCurrentPosition();
SharedPreferences prefs = getSharedPreferences("PrettyGoodMusicPlayer", MODE_PRIVATE);
Log.i(TAG,
"Preferences update success: "
+ prefs.edit()
.putString(songFile.getParentFile().getAbsolutePath(),songFile.getName() + "~" + pos)
.commit());
}
lastResumeUpdateTime = currentTime;
}
}
private void sendUpdateToClients() {
List<Messenger> toRemove = new ArrayList<Messenger>();
synchronized (mClients) {
for (Messenger client : mClients) {
Message msg = Message.obtain(null, MSG_SERVICE_STATUS);
Bundle b = new Bundle();
if (songFile != null) {
b.putString(PRETTY_SONG_NAME,
Utils.getPrettySongName(songFile));
b.putString(PRETTY_ALBUM_NAME, songFile.getParentFile()
.getName());
b.putString(PRETTY_ARTIST_NAME, songFile.getParentFile()
.getParentFile().getName());
} else {
// songFile can be null while we're shutting down.
b.putString(PRETTY_SONG_NAME, " ");
b.putString(PRETTY_ALBUM_NAME, " ");
b.putString(PRETTY_ARTIST_NAME, " ");
}
b.putBoolean(IS_SHUFFLING, this._shuffle);
if (mp.isPlaying()) {
b.putInt(PLAYBACK_STATE, PlaybackState.PLAYING.ordinal());
} else {
b.putInt(PLAYBACK_STATE, PlaybackState.PAUSED.ordinal());
}
// We might not be able to send the position right away if mp is
// still being created
// so instead let's send the last position we knew about.
if (mp.isPlaying()) {
lastDuration = mp.getDuration();
lastPosition = mp.getCurrentPosition();
}
b.putInt(TRACK_DURATION, lastDuration);
b.putInt(TRACK_POSITION, lastPosition);
msg.setData(b);
try {
client.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
toRemove.add(client);
}
}
for (Messenger remove : toRemove) {
mClients.remove(remove);
}
}
}
I got a really helpful response from the developer of the Vanilla Music Player:
We use a separated thread to read-ahead the currently playing file:
-> The thread reads the file with about 256kb/s, so it will read the file faster than mediaserver does
-> This gives the file a very good chance to stay in the page/disk cache
-> ..and this minimizes the chance for 'drop outs' due to funky sd-cards or other IO-pauses.
The code is located here: https://github.com/vanilla-music/vanilla/blob/master/src/ch/blinkenlights/android/vanilla/ReadaheadThread.java
The code does not depend on any parts of vanilla music: if you would like to give it a try, just drop it into your project and do something like:
onCreate {
...
mReadaheadThread = new ReadaheadThread()
...
}
...
mMediaPlayer.setDataSource(path);
mReadaheadThread.setDataSource(path);
...
Since implementing this change I haven't encountered the problem.
I'm trying to get the RemoteControlClient set up so my app's music can be controlled by the widget that pops up on the lock screen (like SoundCloud, Google Play Music, and other music/video apps work). I'm not sure what's wrong with my code and why it isn't correctly hooking, but here's what I have so far...
A class called MusicService that tries to handle the updates to the RemoteControlClient
public class MusicService extends Service
{
public static final String ACTION_PLAY = "com.stfi.music.action.PLAY";
private RemoteController controller = null;
#Override
public void onCreate()
{
super.onCreate();
System.out.println("Creating the service.");
if(controller == null)
{
controller = new RemoteController();
}
}
#Override
public int onStartCommand(Intent intent, int flags, int startId)
{
String action = intent.getAction();
System.out.println("Got an action of " + action);
/* Logic to get my Song cur */
controller.register(this);
controller.updateMetaData(cur);
return START_STICKY;
}
#Override
public void onDestroy()
{
super.onDestroy();
System.out.println("Destorying MusicService");
}
#Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
}
This uses a class I have called RemoteController which houses my RemoteControlClient.
public class RemoteController {
private RemoteControlClient remoteControlClient;
private Bitmap dummyAlbumArt;
public void register(Context context)
{
if (remoteControlClient == null)
{
System.out.println("Trying to register it.");
dummyAlbumArt = BitmapFactory.decodeResource(context.getResources(), R.drawable.dummy_album_art);
AudioManager audioManager = (AudioManager) context.getSystemService(context.AUDIO_SERVICE);
ComponentName myEventReceiver = new ComponentName(context.getPackageName(), MediaButtonReceiver.class.getName());
audioManager.registerMediaButtonEventReceiver(myEventReceiver);
// build the PendingIntent for the remote control client
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.setComponent(myEventReceiver);
// create and register the remote control client
PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(context, 0, mediaButtonIntent, 0);
remoteControlClient = new RemoteControlClient(mediaPendingIntent);
remoteControlClient.setTransportControlFlags(RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
| RemoteControlClient.FLAG_KEY_MEDIA_NEXT
| RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS
| RemoteControlClient.FLAG_KEY_MEDIA_PLAY
| RemoteControlClient.FLAG_KEY_MEDIA_PAUSE
);
audioManager.registerRemoteControlClient(remoteControlClient);
}
}
/**
* Update the state of the remote control.
*/
public void updateState(boolean isPlaying)
{
if(remoteControlClient != null)
{
if (isPlaying)
{
remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING);
}
else
{
remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED);
}
}
}
/**
* Updates the state of the remote control to "stopped".
*/
public void stop()
{
if (remoteControlClient != null)
{
remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED);
}
}
public void updateMetaData(Song song)
{
if (remoteControlClient != null && song != null)
{
System.out.println("Updating metadata");
MetadataEditor editor = remoteControlClient.editMetadata(true);
editor.putBitmap(MetadataEditor.BITMAP_KEY_ARTWORK, dummyAlbumArt);
editor.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, (long)1000);
editor.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, "Artist");
editor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, "Title");
editor.apply();
updateState(true);
}
}
/**
* Release the remote control.
*/
public void release() {
remoteControlClient = null;
}
}
Every time I want to update the widget, I call startService(new Intent(MusicService.ACTION_PLAY));. It looks like it correctly creates the service, and it always gets to the point where it says "Updating metadata", but for some reason when I lock my screen and unlock it, I don't see any widget on my lock screen.
Below is the important parts of my manifest as well, seeing as that could somehow cause the issue...
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.stfi"
android:versionCode="1"
android:versionName="1.0" >
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="17" />
<application
android:hardwareAccelerated="true"
android:allowBackup="true"
android:icon="#drawable/stfi"
android:label="#string/app_name"
android:largeHeap="true"
android:theme="#style/MyActionBarTheme" >
<meta-data
android:name="android.app.default_searchable"
android:value=".activities.SearchActivity" />
<activity
android:name=".StartingToFeelIt"
android:configChanges="orientation|keyboardHidden"
android:label="#string/app_name"
android:screenOrientation="portrait" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="#xml/searchable" />
</activity>
...other activities listed
<service
android:name=".helpers.MyNotificationService"
android:enabled="true"
android:label="MyNotificationServiceLabel" >
</service>
<service
android:name=".music.MusicService"
android:exported="false" >
<intent-filter>
<action android:name="com.stfi.music.action.PLAY" />
</intent-filter>
<intent-filter>
<action android:name="com.example.android.musicplayer.action.URL" />
<data android:scheme="http" />
</intent-filter>
</service>
<receiver
android:name=".music.MediaButtonReceiver"
android:exported="false" >
</receiver>
</application>
Right now my MediaButtonReceiver doesn't really do much of anything. I'm just trying to get the hooks set up. If you want, this is my MediaButtonReceiver class...
public class MediaButtonReceiver extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent)
{
System.out.println("Receiving something.");
if (intent.getAction().equals(Intent.ACTION_MEDIA_BUTTON))
{
final KeyEvent event = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (event != null && event.getAction() == KeyEvent.ACTION_UP)
{
if (event.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE)
{
System.out.println("You clicked pause.");
}
else if(event.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY)
{
System.out.println("You clicked play.");
}
else if (event.getKeyCode() == KeyEvent.KEYCODE_MEDIA_NEXT)
{
System.out.println("You clicked next.");
}
else if (event.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PREVIOUS)
{
System.out.println("You clicked previous.");
}
}
}
}
}
if you can't see remoteControlClient on lock screen you must implement audio focus. You can look here