Keep music playing while changing orientation - android

I'm making music player app for Android and i have problem with keeping music playing while i change orientation of phone.
package nori.beta.musicplayer;
import android.app.Activity;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.graphics.Color;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;
import nori.beta.musicplayer.Class.BlurBuilder;
import nori.beta.musicplayer.Fragment.Playlist;
import nori.beta.musicplayer.Fragment.Utilities;
public class MainActivity extends Activity {
private ImageView bg; // blured backgroud of size of screen
private ImageView cover; // small image in center of activity that plays song
private BlurBuilder blured; // class to blur image for background
private SeekBar
progressBar; // Creating seekbar that show progress of song and allow us scroll and rewind song
private ImageButton
play_pause_stopButton; //on click do 1.play/2.paues/3.stop for all change icon
private MediaPlayer player; // Player that play music
private Handler mHandler = new Handler(); //Handler that help with refreshing progressBar
private Utilities utils; //Change seconds into min + sec
ArrayList<File> mySongs; // list of music file
ArrayList<Song> songsInfo; //list of music file with extract information about them
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// init all GUI staff
initGUI();
setButtons();
}
#Override
protected void onResume() {
super.onResume();
// init Database and rest of gui staff that need database
// Here have u data of all file and chosen song and can u make to play song
}
private void initGUI() {
// Image Part
bg = (ImageView) findViewById(R.id.main_background);
cover = (ImageView) findViewById(R.id.cover_image);
blured = new BlurBuilder();
//Buttons
play_pause_stopButton = (ImageButton) findViewById(R.id.play_pause_stop_button);
progressBar = (SeekBar) findViewById(R.id.progressBar);
player = new MediaPlayer();
utils = new Utilities();
mySongs = findSongs(Environment.getExternalStorageDirectory());
songsInfo = new ArrayList<Song>();
for (File f : mySongs) {
songsInfo.add(new Song(f));
}
//progressBar.setOnSeekBarChangeListener(this);
}
private ArrayList<File> findSongs(File root) {
ArrayList<File> al = new ArrayList<File>();
File[] files = root.listFiles();
/**
* findSongs Search for music file in memory
*
* for each file in memory
* 1.if is that file a folder , then take all file then give it in method findSongs
* and with requrency
* 2.Else if that file end with .mp3 or .wav ,then add to list
*/
for (File singleFile : files) {
if (singleFile.isDirectory() && !singleFile.isHidden()) {
al.addAll(findSongs(singleFile));
//Log.e("findsongs","Folder");
} else {
if (singleFile.getName().endsWith(".mp3") || singleFile.getName().endsWith(".wav")) {
al.add(singleFile);
Log.e("FileInfo.GetSong", singleFile.getName().toString());
}
}
}
return al;
}
private void setButtons() {
play_pause_stopButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) { //Play/Pause song button clicked
playSong(0);
}
});
progressBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { //Using progress bar to scrolling song
#Override
public void onStopTrackingTouch(SeekBar progressBar) {
}
#Override
public void onStartTrackingTouch(SeekBar progressBar) {
}
#Override
public void onProgressChanged(SeekBar progressBar, int progress, boolean fromUser) { //When user move progress bar song go to moment that user choosed
if (player != null && fromUser) {
player.seekTo(progress * 1000);
}
}
});
player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer player) { //When song ended playing
playSong(0);
}
});
}
public void updateProgressBar() {
mHandler.postDelayed(mUpdateTimeTask, 100); //Updating progressBar every 100ms
}
private Runnable mUpdateTimeTask = new Runnable() {
public void run() { //Updating time of song and progressbar
long totalDuration = player.getDuration();
long currentDuration = player.getCurrentPosition();
// Updating progress bar
int mCurrentPosition = player.getCurrentPosition() / 1000;
progressBar.setProgress(mCurrentPosition);
// Running this thread after 100 milliseconds
mHandler.postDelayed(this, 100);
}
};
// set it in all changes of the privius songs <#-- Krzysiek -->
private void setBackground(int i) {
//setting the back image and cover image to the chosen song
if (songsInfo.get(i).getBackground() != null) {
bg.setImageBitmap(blured.blur(this, songsInfo.get(i).getBackground()));
cover.setImageBitmap(songsInfo.get(i).getBackground());
Log.i("FileInfo.SetCover", "Set cover of " + songsInfo.get(i).getName());
}
}
public void playSong(int index) {
try {
player.reset();
player.setDataSource(songsInfo.get(index).getPath()); //Getting song with proper index from list
player.prepare();
player.start(); //Playing prepared song
// Displaying Song title
String songTitle = songsInfo.get(index).getTitle();
String songArtist = songsInfo.get(index).getArtist();
setBackground(index);
// Changing Button Image to pause image
play_pause_stopButton.setImageResource(R.drawable.pause);
// set Progress bar values
progressBar.setProgress(0);
progressBar.setMax(player.getDuration() / 1000);
// Updating progress bar
updateProgressBar();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
I tried with
android:configChanges="orientation|screenSize"
in manifest and it work but only for 1st change from vertical to horizontal orientation. When i change again from horizontal to vertical, same song start from beggining while old is still playing so i have 2 songs played at same moment. Can anyone help me with it?

keep the config changes and add an override to the onconfig...
#Override
public void onConfigurationChanged(Configuration newConfig) {
}
when the orientation changes the oncreate is called by default , you need to override it like this example or maybe use a singleton as a mediaplayer so it won't create two of them

It was just stupid but i just make player static. It helped and now it dont create additional instances

When you orientate your device, it redraw your layouts and that's why activity restarts (calls onConCreate()). You can save your instance with overriding this method:
#Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
}
and restore instance with overriding this method:
#Override
protected void onRestoreInstanceState(#NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
}
I created source in github about this topic, saving instances and restoring. Maybe it'll help you in some circumstances. Orientation Mode

Related

Media Player Button Is Not Restarting

I wanna make "Play/Stop" button. When button "Play" is clicked, the song must be played and its text should be converted into "Stop" and when "Stop" is clicked, the button text should be changed into "Play" again and song should start again to play from the beginning.
import android.media.MediaPlayer;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import java.io.IOException;
public class MainActivity extends AppCompatActivity {
private Button btn_playStop;
private MediaPlayer mediaPlayer;
private boolean flag = false;
#Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActionBar actionBar = getSupportActionBar();
actionBar.setTitle("Play Music");
mediaPlayer = MediaPlayer.create(MainActivity.this, R.raw.zara_sa);
btn_playStop = (Button)findViewById(R.id.btn_play_stop);
btn_playStop.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (mediaPlayer.isPlaying() && flag==true){
stopSong();
}
else if (flag == false){
playSong();
}
}
});
}
public void playSong(){
mediaPlayer.start();
btn_playStop.setText("Stop");
flag = true;
}
public void stopSong() {
mediaPlayer.stop();
btn_playStop.setText("Play");
flag = false;
}
}
You must call mediaPlayer.prepare(); if you called mediaPlayer.stop(); so in the first time you can just call mediaPlayer.start(); but in the next times you should call mediaPlayer.prepare(); before mediaPlayer.start();
public void playSong(){
try {
mediaPlayer.prepare();
} catch (IOException e) {
}
mediaPlayer.start();
btn_playStop.setText("Stop");
flag = true;
}

Android VideoView Activity crashing

I used the tutorial here (https://examples.javacodegeeks.com/android/android-videoview-example/) to build a videoview activity in my app though it keeps crashing when it's opened. I can't figure it out to save my life. I've checked the other posts on here and none of the suggestions have
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.res.Configuration;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnPreparedListener;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.widget.MediaController;
import android.widget.VideoView;
public class DemoActivity extends AppCompatActivity {
private VideoView myVideoView;
private int position = 0;
private ProgressDialog progressDialog;
private MediaController mediaControls;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_quote);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// set the main layout of the activity
setContentView(R.layout.activity_main);
//set the media controller buttons
if (mediaControls == null) {
mediaControls = new MediaController(DemoActivity.this);
}
//initialize the VideoView
myVideoView = (VideoView) findViewById(R.id.video_view);
// create a progress bar while the video file is loading
progressDialog = new ProgressDialog(DemoActivity.this);
// set a title for the progress bar
progressDialog.setTitle("JavaCodeGeeks Android Video View Example");
// set a message for the progress bar
progressDialog.setMessage("Loading...");
//set the progress bar not cancelable on users' touch
progressDialog.setCancelable(false);
// show the progress bar
progressDialog.show();
try {
//set the media controller in the VideoView
myVideoView.setMediaController(mediaControls);
//set the uri of the video to be played
myVideoView.setVideoURI(Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.demo));
} catch (Exception e) {
Log.e("Error", e.getMessage());
e.printStackTrace();
}
myVideoView.requestFocus();
//we also set an setOnPreparedListener in order to know when the video file is ready for playback
myVideoView.setOnPreparedListener(new OnPreparedListener() {
public void onPrepared(MediaPlayer mediaPlayer) {
// close the progress bar and play the video
progressDialog.dismiss();
//if we have a position on savedInstanceState, the video playback should start from here
myVideoView.seekTo(position);
if (position == 0) {
myVideoView.start();
} else {
//if we come from a resumed activity, video playback will be paused
myVideoView.pause();
}
}
});
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
//we use onSaveInstanceState in order to store the video playback position for orientation change
savedInstanceState.putInt("Position", myVideoView.getCurrentPosition());
myVideoView.pause();
}
#Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
//we use onRestoreInstanceState in order to play the video playback from the stored position
position = savedInstanceState.getInt("Position");
myVideoView.seekTo(position);
}
}
In your code have two setContentView() for two layout. That is reason of your error. You need merge two layout and sure that VideoView available on that view.

I am wanting to Run Multiple mediaplayers at once

I am new to Android development and am working on an app that I use for my work. It required multiple buttons to each pay a sound. It is however more complicated than that.
I have managed to make a mediaplayer that will play sounds, pause, fade etc from buttons giving the button tag a string that is passed to the player as file to play. I can press other buttons and start a new sound without problems after stopping and releasing the MP. My problem is As this is for my theatre show. I want to be able to cross mix (i.e as one sound fades the next is starting). The first thought is I need a different MP for each button (which would use a lot of copy code) and also I want to be able to set up nearly 100 buttons.
Has anyone done this before, I have searched for hours online to find very little help. Any help would be useful thank you in advance.
My code is below
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.media.MediaPlayer;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.ActionBar;
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.os.Build;
import android.widget.Button;
import android.widget.TextView;
import android.view.View.OnLongClickListener;
import android.util.Log;
import android.widget.Toast;
import java.io.File;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
public class MainActivity extends Activity {
MediaPlayer mp, mp2 ;
Button Sound1 ,Sound2, Stop, Pause , Fade;
TextView displaystatus;
String bName = "button pressed";
//set variables for volume control
private int iVolume;
private final static int INT_VOLUME_MAX = 100;
private final static int INT_VOLUME_MIN = 0;
private final static float FLOAT_VOLUME_MAX = 1;
private final static float FLOAT_VOLUME_MIN = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//connect interface to local variables
Fade=(Button)findViewById(R.id.bfade);
Sound1 = (Button)findViewById(R.id.bSound1);
Sound2 = (Button)findViewById(R.id.bSound2);
Stop =(Button)findViewById(R.id.bStop); Pause=(Button)findViewById(R.id.bPause);
displaystatus=(TextView)findViewById(R.id.tStatus);
mp2=new MediaPlayer();
//Button clicks to make play/pause/stop
Sound1.setOnClickListener(buttonPlayOnClickListener);
Sound2.setOnClickListener(buttonPlayOnClickListener);
Pause.setOnClickListener(buttonPauseOnClickListener);
Stop.setOnClickListener( buttonQuitOnClickListener);
Fade.setOnClickListener( buttonFadeOnClickListener);
//set onlongclicklistener to open SoundDetailActivity
Sound1.setOnLongClickListener(new View.OnLongClickListener(){
public boolean onLongClick(View v) {
//get the tag for the button pressed
bName= v.getTag().toString();
whenLongClick();
return true;
};
});
//set long click listener to open SoundDetailActivity
Sound2.setOnLongClickListener(new View.OnLongClickListener(){
public boolean onLongClick(View v) {
//get the tag for the button pressed
bName= v.getTag().toString();
whenLongClick();
return true;
};
});
}
private void initMediaPlayer ()
{
if(mp!=null){mp.stop();
mp.release();}
mp = new MediaPlayer();
File path=android.os.Environment.getExternalStorageDirectory();
try {
Log.v("paddy",path+bName);
mp.setDataSource(path+bName );
mp.prepare();
}catch (IOException e){
e.printStackTrace();
}
}
Button.OnClickListener buttonPlayOnClickListener
= new Button.OnClickListener(){
#Override
public void onClick(View v) {
bName= v.getTag().toString();
initMediaPlayer();
Log.v("paddy",bName);
// if(mp.isPlaying()) {mp.reset();}
mp.start();
displaystatus.setText("- PLAYING -");
Pause.setText("Pause");
Log.v("paddy","no sound was playing");
}
};
Button.OnClickListener buttonPauseOnClickListener
= new Button.OnClickListener(){
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
if(mp.isPlaying()) {
mp.pause();
displaystatus.setText("- resume -");
Pause.setText("Resume");
}else{
mp.start();
displaystatus.setText("- playing -");
Pause.setText("Pause");
}
//finish();
}
};
Button.OnClickListener buttonQuitOnClickListener
= new Button.OnClickListener(){
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
mp.stop();
mp.reset();
displaystatus.setText("- Ready -");
}
};
public Button.OnClickListener buttonFadeOnClickListener
=new Button.OnClickListener(){
#Override
public void onClick(View v) {
fade(5000); ///time in milliseconds
// TODO Auto-generated method stub
// mp.stop();
displaystatus.setText("- Fade out -");
//finish();
}
};
public void fade(int fadeDuration)
{
//Set current volume, depending on fade or not
if (fadeDuration > 0)
iVolume = INT_VOLUME_MAX;
else
iVolume = INT_VOLUME_MIN;
updateVolume(0);
//Start increasing volume in increments
if(fadeDuration > 0)
{
final Timer timer = new Timer(true);
TimerTask timerTask = new TimerTask()
{
public void run() {
MainActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
updateVolume(-1);
if (iVolume <= INT_VOLUME_MIN) {
timer.cancel();
timer.purge();
//Pause music
if (mp.isPlaying()) mp.stop();
mp.release();
mp = new MediaPlayer();
mp = MediaPlayer.create(MainActivity.this, R.raw.franksinatra);
displaystatus.setText("- Ready -");
Log.v("paddy","getting to end of fade");
}
}
});
}
};
// calculate delay, cannot be zero, set to 1 if zero
int delay = fadeDuration/INT_VOLUME_MAX;
if (delay == 0) delay = 1;
timer.schedule(timerTask, delay, delay);
}
}
// when a button is longclicked the activity sound details is opened and the sound button tag is sent as an extra.
public void whenLongClick () {
Toast.makeText(getApplicationContext(), bName , Toast.LENGTH_LONG).show();
Intent i = new Intent(this,SoundDetailActivity.class);
i.putExtra("ButtonId",bName);
startActivity(i);
}
private void updateVolume(int change)
{
//increment or decrement depending on type of fade
iVolume = iVolume + change;
//ensure iVolume within boundaries
if (iVolume < INT_VOLUME_MIN)
iVolume = INT_VOLUME_MIN;
else if (iVolume > INT_VOLUME_MAX)
iVolume = INT_VOLUME_MAX;
//convert to float value
float fVolume = 1 - ((float) Math.log(INT_VOLUME_MAX - iVolume) / (float) Math.log(INT_VOLUME_MAX));
//ensure fVolume within boundaries
if (fVolume < FLOAT_VOLUME_MIN)
fVolume = FLOAT_VOLUME_MIN;
else if (fVolume > FLOAT_VOLUME_MAX)
fVolume = FLOAT_VOLUME_MAX;
mp.setVolume(fVolume, fVolume);
}
}

Media Player not working in Android

The idea is to click the spinner and have it give you a list of times to play the sound. once you pick a sound and click the button it should play. it doesnt work and I cant figure out why. I have a home screen and a home activity that when you click a button take you to a new activity and a new layout. I can get the new layout and new activity to load, which is the one featured below, but the sound and spinner do not start.
package com.androidsleepmachine.gamble;
package com.androidsleepmachine.gamble;
import android.app.Activity;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;
public class Ship extends Activity implements View.OnClickListener {
public static final int[] TIME_IN_MINUTES = { 30, 45, 60 };
public MediaPlayer mediaPlayer;
public Handler handler = new Handler();
public Button button1;
public Spinner spinner1;
// Initialize the activity
#Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.ship);
button1 = (Button) findViewById(R.id.btnSubmit);
button1.setOnClickListener(this);
spinner1 = (Spinner) findViewById(R.id.spinner1);
ArrayAdapter<Integer> adapter = new ArrayAdapter<Integer>(this,
android.R.layout.simple_spinner_item);
}
// Play the sound and start the timer
private void playSound(int resourceId) {
// Cleanup any previous sound files
cleanup();
// Create a new media player instance and start it
mediaPlayer = MediaPlayer.create(this, resourceId);
mediaPlayer.start();
// Create the timer to stop the sound after x number of milliseconds
int selectedTime = TIME_IN_MINUTES[spinner1.getSelectedItemPosition()];
handler.postDelayed(runnable, selectedTime * 60 * 1000);
}
// Handle button callbacks
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnSubmit:
playSound(R.raw.ocean_ship);
break;
}
}
// Stop the sound and cleanup the media player
public void cleanup() {
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
// Cancel any previously running tasks
handler.removeCallbacks(runnable);
}
// Runnable task used by the handler to stop the sound
public Runnable runnable = new Runnable() {
public void run() {
cleanup();
}
};
}
It looks like the problem is in your switch statemenet. You are checking for the id of button1 but the only button that you have assigned the listener to is button1 which has an id of btnSubmit. So your playSound() function is never getting called.
Change your onClick() to
// Handle button callbacks
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnSubmit:
playSound(R.raw.ocean_ship);
break;
}
}
Edit
For your ArrayAdapter try something like
ArrayAdapter<Integer> adapter = new ArrayAdapter<Integer>(this, android.R.layout.simple_spinner_item, TIME_IN_MINUTES);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner1.setAdapter(adapter);
Are you doing this on emulator? Mediaplayer doesnt work on emulator. You will have to test this in real device.

android force close on clicking on seekbar

i'm newbie to android programming. i have the following code but i have a problem. when i click on the seekbar without playing the sound it force close. i mean when i click the play button i can click and seek on the seekbar but without clicking the play button clicking on the seekbar will cause force close. what's the problem?!
import android.media.MediaPlayer;
import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageButton;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
public class MainActivity extends Activity implements Runnable, OnClickListener, OnSeekBarChangeListener{
private SeekBar seekBar;
private ImageButton startMedia;
private ImageButton pauseMedia;
private MediaPlayer mp;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AudioControl();
}
public void AudioControl(){
seekBar = (SeekBar) findViewById(R.id.seekBar1);
startMedia = (ImageButton) findViewById(R.id.playbutton);
pauseMedia = (ImageButton) findViewById(R.id.pausebutton);
seekBar.setOnSeekBarChangeListener(this);
startMedia.setOnClickListener(this);
pauseMedia.setOnClickListener(this);
}
public void run() {
int currentPosition= 0;
int total = mp.getDuration();
while (mp!=null && currentPosition<total) {
try {
Thread.sleep(1000);
currentPosition= mp.getCurrentPosition();
} catch (InterruptedException e) {
return;
} catch (Exception e) {
return;
}
seekBar.setProgress(currentPosition);
}
}
public void onClick(View v) {
if (v.equals(startMedia)) {
if (mp != null && mp.isPlaying()) return;
if(seekBar.getProgress() > 0) {
mp.start();
return;
}
mp = MediaPlayer.create(MainActivity.this, R.raw.lone);
mp.start();
seekBar.setProgress(0);
seekBar.setMax(mp.getDuration());
new Thread(this).start();
}
if (v.equals(pauseMedia) && mp!=null) {
mp.pause();
}
}
public void onStartTrackingTouch(SeekBar seekBar) {
}
public void onStopTrackingTouch(SeekBar seekBar) {
}
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
if(fromUser) mp.seekTo(progress);
}
}
and i have another question:
i wanna when the user clicks on play button the pause button become visible and when the user clicks on pause button it tuen invisble. what code should i wirte and please tell me where should i put this code.
Every method that manages the UI has to be called on the UI Thread itself:
You can not run
seekBar.setProgress(currentPosition);
on a Thread different frome the UIThread.

Categories

Resources