Create an Android Ringtone Picker with Ringtonemanager Class
In this article, I will show you how to create a ringtone picker using the RingtoneManager
class in Android. You will be able to get the list of tones, display them as RadioButton
, and let the user pick one tone from the list.
I expect you to have a basic knowledge of programming in Android. You should already have set up Android Studio and all the essential components required to build an Android app.
All code in this article will be in Java and not Kotlin.
Table of Contents
An introduction to the RingtoneManager
class:
Let’s say you are designing an alarm clock app, and you want the user to choose the alarm tone. Android comes with a set of tones that you can display to the user for this purpose. Tones in Android can be classified into ringtone, alarm tone, and notification tone categories. Android comes with the RingtoneManager
class so that you can access these tones.
To show the list of tones under one or more categories, you have to send an implicit intent to launch a ringtone picker. The Intended action will be RingtoneManager.ACTION_RINGTONE_PICKER
. By default, an Android device comes with an activity that will respond to this intent. You can send the following extras with the intent:
EXTRA_RINGTONE_DEFAULT_URI
EXTRA_RINGTONE_EXISTING_URI
EXTRA_RINGTONE_SHOW_DEFAULT
EXTRA_RINGTONE_SHOW_SILENT
EXTRA_RINGTONE_TITLE
EXTRA_RINGTONE_TYPE
For details on what data these extras should carry, please read the documentation of RingtoneManager
the class.
When the user has chosen a tone, the ringtone picker will be destroyed and you will get a callback in the onActivityResult(...)
method of the caller activity. The ringtone picker will return RingtoneManager.
EXTRA_RINGTONE_PICKED_URI
through which you can get the Uri of the tone chosen by the user.
Launching the default ringtone picker using an implicit intent:
The following is a small code snippet showing how to invoke the default ringtone picker from an activity using an implicit intent:
Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALARM); intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, "Select alarm tone:"); intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, existingToneUri); intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false); intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); startActivityForResult(intent, RINGTONE_REQUEST_CODE);
In the above snippet, you can see that I have specified the tone type to be alarm bypassing TYPE_ALARM
with EXTRA_RINGTONE_TYPE
. RingtoneManager
class allows three types of tones: TYPE_ALARM
, TYPE_NOTIFICATION
and TYPE_RINGTONE
. You can combine these to direct the picker to list tones under more than one category:
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALARM | RingtoneManager.TYPE_NOTIFICATION);
Alternatively, you can use TYPE_ALL
it to list all the available tones.
You can handle the activity result in the following way:
@Override public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == RINGTONE_REQUEST_CODE) { if (resultCode == RESULT_OK && data != null) { Uri toneUri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); } } }
Why do you need a custom ringtone picker?
The default ringtone picker will serve your purpose in most cases. However, sometimes you might want to build a custom ringtone picker. Four reasons why I decided to build a custom ringtone picker:
- I wanted to allow the user to pick a file from storage and use it as a tone. This is not possible in the default picker. One can do this easily from regular activity, but I wanted to add this option at the end of the list of tones.
- If I pass the Uri of a file as
EXTRA_RINGTONE_EXISTING_URI
that is not among the default tones, I would like the ringtone picker to include it in the list of tones if the file exists. - The default ringtone picker can be a dialog or an activity. If the user cancels the dialog, you might get a
RESULT_CANCELLED
inonActivityResult(...)
. As per the requirements of my app, I strictly needed the picker to return a Uri even if the user did not choose any Uri/canceled the picker. - Let’s say the user selects a file from the device storage as the tone. When the user launches the ringtone picker next time, it should display all such files chosen previously along with the default tones, provided the files still exist.
Note that I will not cover any of the above features in this article. It will not only make the article very lengthy but may also bore you if you have come to learn the basics only.
Building a ringtone picker activity:
Now, we shall design a simple ringtone picker activity. The activity will have a RadioButton
for each tone. When the user presses the back key or the up button, the activity will return the picked Uri and finish itself. For simplicity, I will not list any import
statements and assume that imports are managed by your IDE. I shall also assume that all the static members of RingtoneManager
class have been imported using:
import static android.media.RingtoneManager.*;
Therefore, from now on, I will address all the static members of this class without using the class name.
At the end, the ringtone picker would look something like this:
Without further ado, let’s start!
Preparing our activity to respond to implicit intents:
Our activity will be called RingtonePickerActivity
, and will be a subclass of androidx.appcompat.app.AppCompatActivity
.
public class RingtonePickerActivity extends AppCompatActivity implements View.OnClickListener{ ... }
View.OnClickListener
will help us to listen to click events. We shall see later where to use it.
We want this activity to respond to any implicit intent that comes with ACTION_RINGTONE_PICKER
, as well as any explicit intent. For the latter, we don’t need to do anything, but for the former, we modify the AndroidManifest.xml
file as follows:
<activity android:name=".RingtonePickerActivity" android:exported="true" android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"> <intent-filter> <action android:name="android.intent.action.RINGTONE_PICKER"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </activity>
- When an implicit intent is sent by some app, the
IntentFilter
will respond to it if the action string matches. android:exported="true"
is required if we want other apps to launch this activity.- We have set the theme to be
Theme.AppCompat.DayNight.NoActionBar
. This will have two advantages:- The activity will support dark (or night) mode.
- There will be no
ActionBar
. We do not want the defaultActionBar
because we will be creating a custom one.
If the theme of your app is the same as this, you need not set the android:theme
attribute separately.
Creating the layout of the activity:
In the layout file of our activity, we will have a Toolbar
that will serve as the ActionBar
of the activity, and a ScrollView
(with vertical scrolling) inside which we will place a RadioGroup
. A ScrollView
can have only one child. So we make that child a ConstraintLayout
inside which we can place the RadioGroup
. Note that we are not putting any RadioButton
inside the RadioGroup
, because we will populate the latter dynamically at runtime.
I always find it convenient to put the child in the ScrollView
in a separate xml
file, and then include
it in the ScrollView
. It will not be quite clear how this is helpful in this simple activity, but in a complex layout, where you have multiple views inside the child of the ScrollView
, it helps if you separately design the views and include them in the ScrollView
.
Therefore, we will have two xml
files:
File activity_ringtonepicker
:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar4" android:layout_width="0dp" android:layout_height="wrap_content" android:background="?attr/colorPrimary" android:minHeight="?attr/actionBarSize" android:theme="?attr/actionBarTheme" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <ScrollView android:id="@+id/addAlarmScrollView" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="0sp" android:layout_marginTop="1dp" android:layout_marginEnd="0sp" android:fitsSystemWindows="false" android:scrollbars="vertical" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="1.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/toolbar4" app:layout_constraintVertical_bias="0.0"> <include layout="@layout/activity_ringtonepicker_scrollview"/> </ScrollView> </androidx.constraintlayout.widget.ConstraintLayout>
A piece of advice: If this is the first time you are working with ScrollView
, let me draw your attention to android:fitsSystemWindows
attribute in the above code. If this is not set to false
, the scrolling won’t work (by default, it is true
). Also, make sure you mention the type of scrolling using the android:scrollbars
attribute.
File activity_ringtonepicker_scrollview
:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <RadioGroup android:id="@+id/ringtonePickerRadioGroup" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="2dp" android:divider="?android:attr/dividerHorizontal" android:dividerPadding="30dp" android:orientation="vertical" android:showDividers="middle" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <!-- You can place more static views here. --> </androidx.constraintlayout.widget.ConstraintLayout>
Make a note of the android:divider
, android:showDividers
and android:dividerPadding
attributes in the above xml
file. Without these, the dividers between the RadioButtons that you see in the above picture won’t appear. Also, make sure you specify android:orientation
to be vertical
.
The .java
file:
We have created the activity layout file and edited the manifest file to suit our needs. Now we can dive into the real programming part.
Declaring global variables:
I am in no mood to increase the length of this article indefinitely, but I cannot proceed further before giving a list of the global variables that we will be using later.
private Uri defaultUri, existingUri, pickedUri; private boolean showDefault, showSilent, wasExistingUriGiven; private CharSequence title; private int type; private ArrayList<Uri> toneUriList; private ArrayList<String> toneNameList; private ArrayList<Integer> toneIdList; private RingtoneManager ringtoneManager; private RadioGroup radioGroup; private MediaPlayer mediaPlayer; private AudioAttributes audioAttributes; private static final int DEFAULT_RADIO_BTN_ID = View.generateViewId(), SILENT_RADIO_BTN_ID = View.generateViewId();
A few words about the global variables (you may skip this if you are not interested):
- Variables
defaultUri
,existingUri
,showDefault
,showSilent
,title
andtype
will be read fromEXTRA_RINGTONE_DEFAULT_URI
,EXTRA_RINGTONE_EXISTING_URI
,EXTRA_RINGTONE_SHOW_DEFAULT
,EXTRA_RINGTONE_SHOW_SILENT
,EXTRA_RINGTONE_TITLE
andEXTRA_RINGTONE_TYPE
respectively, or assigned default values if any of these extras are missing. - Variable
pickedUri
will contain the Uri of the tone picked by the user. toneUriList
is anArrayList
of the unique Uri of the tone files returned byRingtoneManager
.toneNameList
contains the corresponding names of the tones that can be displayed to the user in the form of RadioButtons.toneIdList
is anArrayList
of ids that will be used for setting the id of RadioButtons.DEFAULT_RADIO_BTN_ID
andSILENT_RADIO_BTN_ID
are the ids for the “Default” and the “Silent”RadioButton
respectively inradioGroup
.wasExistingUriGiven
will help us determine whether our activity had receivedEXTRA_RINGTONE_EXISTING_URI
. This variable will be particularly helpful if the Uri passed isnull
or if the extra is not sent.- When the user chooses a tone, we will play the tone using
mediaPlayer
. TheaudioAttributes
will be required to set the stream of themediaPlayer
.
How to get the list of tones using RingtoneManager:
All the codes in this section will be in the onCreate(...)
method of our activity, unless otherwise noted.
We use the constructor of RingtoneManager
class that takes an Activity
as the argument, and thereby initialise the ringtoneManager
:
ringtoneManager = new RingtoneManager(this);
Next, we need to set the type of tone we want. Generally, we will get this using EXTRA_RINGTONE_TYPE
:
if (getIntent().hasExtra(EXTRA_RINGTONE_TYPE)) { type = (int) Objects.requireNonNull(getIntent().getExtras()).get(EXTRA_RINGTONE_TYPE); } else { type = TYPE_ALL; } ringtoneManager.setType(type);
Now we can extract the list of tones using the getCursor()
method of RingtoneManager
class:
Cursor tonesCursor = ringtoneManager.getCursor();
In Android, the Cursor
interface is used to access the result set returned by a database query. You can think of it as a table with several columns. Each column has a unique index (an integer), and the data of that column can be accessed using the column index.
The Cursor
returned by RingtoneManager
(variable tonesCursor
in our case) has three columns with indices RingtoneManager.TITLE_COLUMN_INDEX
, RingtoneManager.URI_COLUMN_INDEX
and RingtoneManager.ID_COLUMN_INDEX
. The first index will return the displayable name of the tone, the second will give the media provider’s Uri, and the third will give the row ID. We will fill up the variables toneNameList
, toneUriList
and toneIdList
in the following way:
Thread thread = new Thread(() -> { if (tonesCursor.moveToFirst()) { do { int id = tonesCursor.getInt(ID_COLUMN_INDEX); String uri = tonesCursor.getString(URI_COLUMN_INDEX); toneUriList.add(Uri.parse(uri + "/" + id)); toneNameList.add(tonesCursor.getString(TITLE_COLUMN_INDEX)); toneIdList.add(View.generateViewId()); } while (tonesCursor.moveToNext()); } }); thread.start();
Note that URI_COLUMN_INDEX
will give you the media provider’s Uri, not the unique Uri to a tone file. You have to append it with ID_COLUMN_INDEX
to get the complete Uri of the file, as shown above.
Half of our work is done once we have got the tones. We still have to display the tones as RadioButtons, play the tone when the user chooses one, handle their click events and return the result to the caller activity when the user presses the back/up button.
Initializing the mediaPlayer
:
We will initialize the media player in the onCreate(...)
of our activity.
mediaPlayer = new MediaPlayer(); audioAttributes = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .build();
Complete the code of the onCreate(...)
method of our activity:
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_ringtonepicker); setSupportActionBar(findViewById(R.id.toolbar4)); Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true); radioGroup = findViewById(R.id.ringtonePickerRadioGroup); ringtoneManager = new RingtoneManager(this); mediaPlayer = new MediaPlayer(); audioAttributes = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .build(); Intent intent = getIntent(); // get the Intent that started this activity. Cursor tonesCursor; toneNameList = new ArrayList<>(); toneUriList = new ArrayList<>(); toneIdList = new ArrayList<>(); if (Objects.equals(getIntent().getAction(), ACTION_RINGTONE_PICKER)) { if (intent.hasExtra(EXTRA_RINGTONE_TYPE)) { type = (int) Objects.requireNonNull(intent.getExtras()).get(EXTRA_RINGTONE_TYPE); } else { type = TYPE_ALL; } ringtoneManager.setType(type); allTonesCursor = ringtoneManager.getCursor(); Thread thread = new Thread(() -> { if (allTonesCursor.moveToFirst()) { do { int id = tonesCursor.getInt(ID_COLUMN_INDEX); String uri = tonesCursor.getString(URI_COLUMN_INDEX); toneUriList.add(Uri.parse(uri + "/" + id)); toneNameList.add(tonesCursor.getString(TITLE_COLUMN_INDEX)); toneIdList.add(tonesCursor.getInt(ID_COLUMN_INDEX)); } while (tonesCursor.moveToNext()); } }); thread.start(); if (intent.hasExtra(EXTRA_RINGTONE_SHOW_DEFAULT)) { showDefault = (boolean) Objects.requireNonNull(intent.getExtras()).get(EXTRA_RINGTONE_SHOW_DEFAULT); } else { showDefault = true; } if (intent.hasExtra(EXTRA_RINGTONE_SHOW_SILENT)) { showSilent = (boolean) Objects.requireNonNull(intent.getExtras()).get(EXTRA_RINGTONE_SHOW_SILENT); } else { showSilent = false; } if (showDefault) { if (intent.hasExtra(EXTRA_RINGTONE_DEFAULT_URI)) { defaultUri = Objects.requireNonNull(intent.getExtras()).getParcelable(EXTRA_RINGTONE_DEFAULT_URI); } else { defaultUri = RingtoneManager.getActualDefaultRingtoneUri(this, type); } } else { defaultUri = null; } if (intent.hasExtra(EXTRA_RINGTONE_EXISTING_URI)) { existingUri = Objects.requireNonNull(intent.getExtras()).getParcelable(EXTRA_RINGTONE_EXISTING_URI); wasExistingUriGiven = true; } else { wasExistingUriGiven = false; existingUri = null; } if (intent.hasExtra(EXTRA_RINGTONE_TITLE)) { title = (CharSequence) Objects.requireNonNull(intent.getExtras()).get(EXTRA_RINGTONE_TITLE); } else { title = "Select tone:"; } Objects.requireNonNull(getSupportActionBar()).setTitle(title); try { thread.join(); } catch (InterruptedException ignored) { } } }
Populate the RadioGroup
using the tones:
Now that we have the tones, we can start populating radioGroup
.
How to add a RadioButton
to a RadioGroup
dynamically?
We will use the addView(View view)
method in RadioGroup
class to add RadioButtons. Each RadioButton must have an ID; using this ID we can later find out which button was clicked. To simplify our task, we will be using the values in toneIdList
as the id of the RadioButtons. The “Default” and “Silent” buttons, if present, will have the ids DEFAULT_RADIO_BTN_ID
and SILENT_RADIO_BTN_ID
respectively.
We shall use the following method to create a RadioButton
with a given text
and id
, add it to radioGroup
and set the OnClickListener
:
/** * Creates one {@link RadioButton} and adds it to {@link #radioGroup}. * * @param id The id to be assigned to the {@link RadioButton}. * @param text The text to be set in the {@link RadioButton}. */ private void createOneRadioButton(int id, String text) { RadioGroup.LayoutParams params = new RadioGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); params.setMargins(5, 24, 5, 24); RadioButton radioButton = new RadioButton(this); radioButton.setId(id); radioButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 17); // set text size to 17sp radioButton.setLayoutParams(params); radioButton.setText(text); radioButton.setOnClickListener(this); radioGroup.addView(radioButton); }
Populate radioGroup
:
Now it’s pretty straightforward: For each tone in our list, we call the above method. If showDefault
and showSlient
are both set to true
, we add the “Default” and “Silent” RadioButtons:
if (showDefault) { createOneRadioButton(DEFAULT_RADIO_BTN_ID, getResources().getString(R.string.defaultTone)); } if (showSilent) { createOneRadioButton(SILENT_RADIO_BTN_ID, getResources().getString(R.string.silentTone)); } for (int i = 0; i < toneIdList.size(); i++) { createOneRadioButton(toneIdList.get(i), toneNameList.get(i)); }
Check the appropriate RadioButton
if the user has passed an existingUri
:
If an existingUri
has been passed to our activity, we need to check the appropriate RadioButton
. We can do this as follows:
if (existingUri != null) { if (showDefault && existingUri.equals(defaultUri)) { /////////////////////////////////////////////////////////////////////////// // The existingUri is same as defaultUri, and showDefault is true. // So, we check the "Default" RadioButton. ////////////////////////////////////////////////////////////////////////// ((RadioButton) findViewById(DEFAULT_RADIO_BTN_ID)).setChecked(true); pickedUri = defaultUri; } else { // Find index of existingUri in toneUriList int index = toneUriList.indexOf(existingUri); if (index != - 1) { // toneUriList has existingUri. Check the corresponding RadioButton. ((RadioButton) findViewById(toneIdList.get(index))).setChecked(true); pickedUri = existingUri; } } } else { //existingUri is null. if (wasExistingUriGiven) { ////////////////////////////////////////////////////////////////////////// // existingUri was specifically passed as a null value. If showSilent // is true, we pre-select the "Silent" RadioButton. Otherwise // we do not select any specific RadioButton. ///////////////////////////////////////////////////////////////////////// if (showSilent) { ((RadioButton) findViewById(SILENT_RADIO_BTN_ID)).setChecked(true); } } pickedUri = null; }
We face a problem if existingUri
is null
, because it can be null
for two reasons:
- The calling activity has deliberately passed
null
. - The calling activity has not passed the
EXTRA_EXISTING_URI
.
We can distinguish between the first and the second cases using the variable wasExistingUriGiven
. As explained earlier, if this yields true
, it means that we have received EXTRA_EXISTING_URI
, and we have to check whether showSilent
is true
or not. If it is true
, we should check the “Silent” RadioButton. Otherwise, we will just ignore this.
Handling RadioButton
click events:
Each of the RadioButtons in radioGroup
has an OnClickListener
. We have to handle their click events inside the onClick(View view)
method, which we override from the View.OnClickListener
interface.
@Override public void onClick(View view) { if (view.getId() == DEFAULT_RADIO_BTN_ID) { pickedUri = defaultUri; playChosenTone(); } else if (view.getId() == SILENT_RADIO_BTN_ID) { pickedUri = null; } else { pickedUri = toneUriList.get(toneIdList.indexOf(view.getId())); playChosenTone(); } }
Play the tone chosen by the user:
In the above section, you can see that we are calling a method playChosenTone()
. This method is used to play the tone chosen by the user.
private void playChosenTone() { if (pickedUri != null) { try { mediaPlayer.reset(); mediaPlayer.setDataSource(this, pickedUri); mediaPlayer.setLooping(false); mediaPlayer.setAudioAttributes(audioAttributes); mediaPlayer.prepare(); mediaPlayer.start(); } catch (Exception ignored) { } } }
The above code will play the chosen tone. However, remember that if the user exits the activity (by pressing back button, up button or home key), we need to stop the media player immediately. Therefore, in the onPause()
of our activity, we put the following code:
@Override protected void onPause() { super.onPause(); try { mediaPlayer.stop(); } catch (Exception ignored) { } }
In some cases, the above may not work. You can try using mediaPlayer.pause()
instead of stop()
.
When the activity finishes, we destroy the media player and release its resources:
@Override protected void onDestroy() { super.onDestroy(); try { mediaPlayer.release(); } catch (Exception ignored) { } }
Returning the result when the back button is pressed:
We can listen to click events of the back button by overriding the onBackPressed()
method in an activity. In this method, we shall set the result. The pickedUri
will be returned using EXTRA_RINGTONE_PICKED_URI
. If pickedUri
is null, we check whether showSilent
is true
or not. If it is false
, it means that the calling activity does not expect a null
value, so we return RESULT_CANCELLED
. In all other cases, we return RESULT_OK
along with pickedUri
.
@Override public void onBackPressed() { if (pickedUri == null) { if (showSilent) { Intent intent = new Intent(); intent.putExtra(EXTRA_RINGTONE_PICKED_URI, pickedUri); setResult(RESULT_OK, intent); } else { setResult(RESULT_CANCELED); } } else { Intent intent = new Intent(); intent.putExtra(EXTRA_RINGTONE_PICKED_URI, pickedUri); setResult(RESULT_OK, intent); } finish(); }
Handle up button click events:
In the onCreate(...)
method above, we have the following code:
Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
We need to listen to up button click events. Normally we expect the up button to do the same job as the back button, so we just call onBackPressed()
:
@Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { if (item.getItemId() == android.R.id.home) { this.onBackPressed(); return true; } return super.onOptionsItemSelected(item); }
That’s it! Our simple ringtone picker activity is complete.
Final words:
I hope I have been able to help you build a ringtone picker activity in this article. The activity, in the above form, does the same job as the default ringtone picker. Now you can add as many features to it as you want.
Note that this code is not safe from configuration changes. This means that the activity will malfunction if the screen orientation or night mode changes. You will have to handle configuration changes yourself by making the necessary alterations in the above code. You can have a look at the Android developer guide on how to handle config changes.
I would like to thank Greg Bernhardt, the owner, and admin of Physics Forums, for creating and maintaining the Insights Blog and for constantly motivating us to share our knowledge by writing articles in his blog.
Questions, comments, suggestions for improvements, and bug reports are always welcome.
Studying physics at a college in Kolkata, India. Interested in accelerator physics, beam instrumentation and diagnostics, and particle physics detector design. Also passionate in electronics and programming in Java, Android, MATLAB and Python.
Is it possible for a standard Android app (one built on the regular dev tools) to turn on and turn off the charging current? And if so, would it work without rooting the device?
"
Unfortunately Android does not provide any standard way of controlling charge current. All you can do is read the battery status (charging/discharging), battery health, and charge percentage, and for these Android provides the
BatteryManager
class, and three broadcasts:ACTION_BATTERY_CHANGED
,ACTION_BATTERY_LOW
andACTION_BATTERY_OKAY
.Even if your phone is rooted, there is no standard way of doing this, because rooting itself is considered non-standard by the Android devs. There are Magisk modules that can control charge by modifying the firmware.
May I suggest an Insights tutorial aimed at people who would like to get started with Android programming (assuming they have some prior programming on other platforms).
"
Good idea, but it cannot be one tutorial; if I write all the basics of Android in one article, it would become as long as LHC's technical design report.
One thing that kills the enthusiasm to some extent is that there is already a huge number of tutorials available on the net that will help you to get started with Android.
The biggest problem when one starts programming in Android is to set up Android Studio. If you have previously worked with Intellij IDEA and Gradle, then you will face lesser problems, but for someone going from NetBeans environment, it would be a big jump.
Anyways, thanks for the suggestion; I will surely look into it.