I recently developed an android app that streams radio online using a URL. It has been working well in other android versions that is 6,7and 8 the ones i tested in emulators.
But last week i published the app to play store, and my reports show that it crashes in android 9 phones. It keeps on throwing java.lang.SecurityException. I have tried what i could to resolve the error but i have failed. The users keep reporting multiple app crashes on their phones
This is the stack trace from the play console
java.lang.RuntimeException:
at android.app.ActivityThread.handleServiceArgs (ActivityThread.java:3903)
at android.app.ActivityThread.access$1700 (ActivityThread.java:236)
at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1815)
at android.os.Handler.dispatchMessage (Handler.java:106)
at android.os.Looper.loop (Looper.java:214)
at android.app.ActivityThread.main (ActivityThread.java:7032)
at java.lang.reflect.Method.invoke (Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:494)
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:965)
Caused by: java.lang.SecurityException:
at android.os.Parcel.createException (Parcel.java:1966)
at android.os.Parcel.readException (Parcel.java:1934)
at android.os.Parcel.readException (Parcel.java:1884)
at android.app.IActivityManager$Stub$Proxy.setServiceForeground (IActivityManager.java:5043)
at android.app.Service.startForeground (Service.java:695)
at com.premar.radiomunabuddu.RadioMediaPlayerService.play (RadioMediaPlayerService.java:120)
at com.premar.radiomunabuddu.RadioMediaPlayerService.onStartCommand (RadioMediaPlayerService.java:50)
at android.app.ActivityThread.handleServiceArgs (ActivityThread.java:3884)
Caused by: android.os.RemoteException:
at com.android.server.am.ActivityManagerService.enforcePermission (ActivityManagerService.java:12159)
at com.android.server.am.ActiveServices.setServiceForegroundInnerLocked (ActiveServices.java:1289)
at com.android.server.am.ActiveServices.setServiceForegroundLocked (ActiveServices.java:969)
at com.android.server.am.ActivityManagerService.setServiceForeground (ActivityManagerService.java:24839)
at android.app.IActivityManager$Stub.onTransact$setServiceForeground$ (IActivityManager.java:11378)
This is the HomeActivity.java
public class HomeActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener {
FancyButton listenRadio;
ImageView facebook, twitter, instagram, linkedin;
RadioSettings settings;
Context context;
public static final int REQUEST_CODE =123;
private Button stopButton = null;
private Button playButton = null;
private Button phoneCall;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ButterKnife.bind(this);
settings = new RadioSettings();
//views
phoneCall = (Button)this.findViewById(R.id.phoneBtn);
//Allow hardware audio buttons to control volume
setVolumeControlStream(AudioManager.STREAM_MUSIC);
clickListeners(); //Start click listeners
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.addDrawerListener(toggle);
toggle.syncState();
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
}
private void openFacebookProfile() {
try {
String facebookURL = getFacebookPageUrl();
Intent facebookIntent = new Intent(Intent.ACTION_VIEW);
facebookIntent.setData(Uri.parse(facebookURL));
startActivity(facebookIntent);
} catch (Exception e){
e.printStackTrace();
}
}
private String getFacebookPageUrl() {
final String facebookUrl = settings.getFacebookAddress();
String fbURL = null;
PackageManager packageManager = getPackageManager();
try {
if (packageManager != null){
Intent fbIntent = packageManager.getLaunchIntentForPackage("com.facebook.katana");
if (fbIntent != null){
int versionCode = packageManager.getPackageInfo("com.facebook.katana",0).versionCode;
if (versionCode >= 3002850){
fbURL = "fb://page/1993598950880589";
}
} else {
fbURL = facebookUrl;
}
}
else {
fbURL = facebookUrl;
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
fbURL = facebookUrl;
}
return fbURL;
}
private void openTwitterProfile(){
Intent intent = null;
try {
this.getPackageManager().getPackageInfo("com.twitter.android", 0);
intent = new Intent(Intent.ACTION_VIEW, Uri.parse("twitter://user?user_id=USERID"));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
} catch (Exception e){
intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://twitter.com/profilename"));
}
this.startActivity(intent);
}
@Override
public void onBackPressed() {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.home, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.watch_webcam: {
launchWebcam();
break;
}
case R.id.playstore_share: {
/*
Uri uri = Uri.parse("market://details?id=" + context.getPackageName());
Intent goToMarket = new Intent(Intent.ACTION_VIEW, uri);
// To count with Play market backstack, After pressing back button,
// to taken back to our application, we need to add following flags to intent.
goToMarket.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY |
Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
try {
startActivity(goToMarket);
} catch (ActivityNotFoundException e) {
startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse("http://play.google.com/store/apps/details?id=" + context.getPackageName())));
}
break;
*/
}
}
return super.onOptionsItemSelected(item);
}
@SuppressWarnings("StatementWithEmptyBody")
@Override
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
int id = item.getItemId();
if (id == R.id.nav_share) {
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
String shareMessage= "\nPlease download our Radiomunnabuddu USA app from the Play Store\n\n";
shareMessage = shareMessage + "https://play.google.com/store/apps/details?id=" + BuildConfig.APPLICATION_ID +"\n\n";
shareIntent.putExtra(Intent.EXTRA_TEXT , shareMessage);
shareIntent.putExtra(Intent.EXTRA_SUBJECT, "Radio Munnabuddu USA");
startActivity(Intent.createChooser(shareIntent, "Share via..."));
}
else if (id == R.id.nav_email){
Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
emailIntent.setData(Uri.parse("mailto: "+settings.getEmailAddress()));
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Radio Munnabuddu USA");
if (emailIntent.resolveActivity(getPackageManager()) != null){
startActivity(Intent.createChooser(emailIntent, "Send email via"));
}
}
else if(id == R.id.nav_report){
Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
emailIntent.setData(Uri.parse("mailto: denis@premar.tech"));
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Crash or Bug report");
if (emailIntent.resolveActivity(getPackageManager()) != null){
startActivity(Intent.createChooser(emailIntent, "Send email via."));
}
}
else if(id == R.id.nav_about){
Intent aboutIntent = new Intent(HomeActivity.this, AboutActivity.class);
startActivity(aboutIntent);
}
else if(id == R.id.nav_fb){
openFacebookProfile();
}
else if(id == R.id.nav_twitter){
openTwitterProfile();
}
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
return true;
}
/**
* Listens for contact button clicks
*/
private void clickListeners(){
//Play button
playButton = (Button)findViewById(R.id.PlayButton);
playButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(),
RadioMediaPlayerService.class);
intent.putExtra(RadioMediaPlayerService.START_PLAY, true);
startService(intent);
}
});
//Stop button
stopButton = (Button)findViewById(R.id.StopButton);
stopButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
//Get new MediaPlayerService activity
Intent intent = new Intent(getApplicationContext(),
RadioMediaPlayerService.class);
stopService(intent);
}
});
//Email Button click list
final View EmailPress = (Button)this.findViewById(R.id.emailBtn);
EmailPress.setOnClickListener(new View.OnClickListener() {
public void onClick(View view){
Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
emailIntent.setData(Uri.parse("mailto: "+settings.getEmailAddress()));
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Radio Munnabuddu");
if (emailIntent.resolveActivity(getPackageManager()) != null){
try {
startActivity(Intent.createChooser(emailIntent, "Send email..."));
} catch (android.content.ActivityNotFoundException ex) {
Toast.makeText(HomeActivity.this, "There are no email clients installed.", Toast.LENGTH_SHORT).show();
}
}
}
});
//Website Button
final View WWWPress = (Button)this.findViewById(R.id.websiteBtn);
WWWPress.setOnClickListener(new View.OnClickListener() {
public void onClick(View view){
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(settings.getWebsiteURL())); //URL
startActivity (browserIntent);
}
});
//SMS Button
final View TxtPress = (Button)this.findViewById(R.id.txtBtn);
TxtPress.setOnClickListener(new View.OnClickListener() {
public void onClick(View view){
Uri uri = Uri.parse(settings.getSmsNumber());
Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
intent.putExtra("sms_body", "Hello Presenter,");
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}
/*
if (ActivityCompat.checkSelfPermission(HomeActivity.this, Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED){
Toast.makeText(HomeActivity.this, "Please grant the permission to call", Toast.LENGTH_SHORT).show();
requestSMSPermission();
}
else {
startActivity (smsIntent);
}*/
}
});
}
/**
* Launches webcam from external URL
*/
public void launchWebcam(){
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(settings.getRadioWebcamURL()));
startActivity (browserIntent);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE:
if ((grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
onCall();
} else {
Log.d("TAG", "Call Permission Not Granted");
//Toast.makeText(this, "Call Permission Not Granted", Toast.LENGTH_SHORT).show();
}
return;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
public void onCall() {
int permissionCheck = ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE);
if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.CALL_PHONE},
REQUEST_CODE);
} else {
phoneCall.setOnClickListener(new View.OnClickListener() {
public void onClick(View view){
/*
String phoneNum = settings.getPhoneNumber();
Intent phoneIntent = new Intent(Intent.ACTION_CALL);
phoneIntent.setData(Uri.parse("tel:"+ phoneNum));
if (phoneIntent.resolveActivity(getPackageManager()) != null) {
startActivity(phoneIntent);
}
*/
startActivity(new Intent(Intent.ACTION_CALL).setData(Uri.parse("tel:" + settings.getPhoneNumber())));
}
});
}
}
This is the RadioMediaPlayerService.java
public class RadioMediaPlayerService extends Service implements
AudioManager.OnAudioFocusChangeListener {
//Variables
private boolean isPlaying = false;
private MediaPlayer radioPlayer; //The media player instance
private static int classID = 579; // just a number
public static String START_PLAY = "START_PLAY";
AudioManager audioManager;
//Media session
MediaSession mSession;
//Settings
RadioSettings settings = new RadioSettings();
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* Starts the streaming service
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent.getBooleanExtra(START_PLAY, false)) {
play();
}
//Request audio focus
if (!requestAudioFocus()) {
//Could not gain focus
stopSelf();
}
return Service.START_STICKY;
}
@Override
public void onCreate() {
super.onCreate();
}
/**
* Starts radio URL stream
*/
private void play() {
//Check connectivity status
if (isOnline()) {
//Check if player already streaming
if (!isPlaying) {
isPlaying = true;
//Return to the current activity
Intent intent = new Intent(this, HomeActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|
Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
// mSession.setSessionActivity(pi);
//Build and show notification for radio playing
Bitmap largeIcon = BitmapFactory.decodeResource(getResources(),
R.drawable.buddu3);
Notification notification = new NotificationCompat.Builder(this, "ID")
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setTicker("Radio Munnabuddu USA")
.setContentTitle(settings.getRadioName())
.setContentText(settings.getMainNotificationMessage())
.setSmallIcon(R.drawable.ic_radio_black_24dp)
//.addAction(R.drawable.ic_play_arrow_white_64dp, "Play", pi)
// .addAction(R.drawable.ic_pause_black_24dp, "Pause", pi)
.setLargeIcon(largeIcon)
.setContentIntent(pi)
.build();
//Get stream URL
radioPlayer = new MediaPlayer();
try {
radioPlayer.setDataSource(settings.getRadioStreamURL()); //Place URL here
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
radioPlayer.prepareAsync();
radioPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
public void onPrepared(MediaPlayer mp) {
radioPlayer.start(); //Start radio stream
}
});
startForeground(classID, notification);
//Display toast notification
Toast.makeText(getApplicationContext(), settings.getPlayNotificationMessage(),
Toast.LENGTH_LONG).show();
}
}
else {
//Display no connectivity warning
Toast.makeText(getApplicationContext(), "No internet connection",
Toast.LENGTH_LONG).show();
}
}
/**
* Stops the stream if activity destroyed
*/
@Override
public void onDestroy() {
stop();
removeAudioFocus();
}
/**
* Stops audio from the active service
*/
private void stop() {
if (isPlaying) {
isPlaying = false;
if (radioPlayer != null) {
radioPlayer.release();
radioPlayer = null;
}
stopForeground(true);
}
Toast.makeText(getApplicationContext(), "Radio stopped",
Toast.LENGTH_LONG).show();
}
/**
* Checks if there is a data or internet connection before starting the stream.
* Displays Toast warning if there is no connection
* @return online status boolean
*/
public boolean isOnline() {
ConnectivityManager cm =
(ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = cm.getActiveNetworkInfo();
if (netInfo != null && netInfo.isConnectedOrConnecting()) {
return true;
}
return false;
}
@Override
public void onAudioFocusChange(int focusChange) {
//Invoked when the audio focus of the system is updated.
switch (focusChange) {
/*
case AudioManager.AUDIOFOCUS_GAIN:
// resume playback
// if (radioPlayer == null) initMediaPlayer();
if (radioPlayer.isPlaying()){
radioPlayer.release();
stopForeground(true);
}
radioPlayer.setVolume(1.0f, 1.0f);
break;*/
case AudioManager.AUDIOFOCUS_LOSS:
// Lost focus for an unbounded amount of time: stop playback and release media player
if (radioPlayer.isPlaying()) radioPlayer.stop();
radioPlayer.release();
//radioPlayer = null;
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
// Lost focus for a short time, but we have to stop
// playback. We don't release the media player because playback
// is likely to resume
if (radioPlayer.isPlaying()) radioPlayer.pause();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// Lost focus for a short time, but it's ok to keep playing
// at an attenuated level
if (radioPlayer.isPlaying()) radioPlayer.setVolume(0.1f, 0.1f);
break;
}
}
/**
* AudioFocus
*/
private boolean requestAudioFocus() {
audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
//Focus gained
return true;
}
//Could not gain focus
return false;
}
private boolean removeAudioFocus() {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
audioManager.abandonAudioFocus(this);
}
}
This is the Manifest.xml
<!--uses-permission android:name="android.permission.SEND_SMS" /-->
<uses-permission android:name="android.permission.INTERNET" />
<!--uses-permission android:name="android.permission.CALL_PHONE" /-->
<!--tools:node="remove"-->
<uses-feature
android:name="android.hardware.telephony"
android:required="false" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_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"
tools:ignore="GoogleAppIndexingWarning">
<service
android:name="com.premar.radiomunabuddu.RadioMediaPlayerService"
android:enabled="true" >
</service>
<receiver android:name="com.premar.radiomunabuddu.IntentReceiver">
<intent-filter>
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>
</receiver>
<activity
android:name="com.premar.radiomunabuddu.HomeActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.premar.radiomunabuddu.AboutActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.premar.radiomunabuddu.HomeActivity" />
</activity>
</application>
Android 9 introduced a new FOREGROUND_SERVICE
permission. From the docs:
Note: Apps that target Android 9 (API level 28) or higher and use foreground services must request the FOREGROUND_SERVICE permission. This is a normal permission, so the system automatically grants it to the requesting app.
If an app that targets API level 28 or higher attempts to create a foreground service without requesting FOREGROUND_SERVICE, the system throws a SecurityException.
Just add that permission to your manifest and you should be good to go.