I'm developing an Android Widget for an app, and the problem is I can't set onClickPendingIntent() on a button in a RemoteViewsFactory.
I explain: I created an AppWidgetProvider, which calls an extending of RemoteViewsService which calls an extending of RemoteViewsFactory for complete a ListView in my widget.
The RemoteViewsFactory have to return all items for update or create them and display on the widget. But for each items of the list view, I have 2 types of buttons:
A button which opens gmaps/dialer/sms (It works).
A button which calls an activity in my app and send it in a parameter the ID of the item.
And the problem is the second button, my solution doesn't work.
Here is the problem:
And so, this is the code which doesn't work:
row.setOnClickPendingIntent(R.id.taskButton, onClickPendingIntent);
// Creating an onclick event for the done button
Intent doneIntent = new Intent(mContext, WidgetProvider.class);
doneIntent.putExtra("DONE_TASK", "DOOOOM");
PendingIntent onDoneIntent = PendingIntent.getActivity(mContext, 0, doneIntent, 0);
row.setOnClickPendingIntent(R.id.doneButton, onDoneIntent);
Here is the complete WidgetFactory class:
import java.util.Collections;
import java.util.Comparator;
import java.util.GregorianCalendar;
import java.util.Vector;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.view.View;
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;
public class WidgetFactory implements RemoteViewsService.RemoteViewsFactory {
private Vector<EventInfo> mAllEvents;
private Context mContext;
public WidgetFactory(Context ctxt, Intent intent) {
// Creating member vars
mContext = ctxt;
updateAllEventsVector();
}
private void updateAllEventsVector() {
SharedInstances sharedInstances = SharedInstances.get();
mAllEvents = new Vector<EventInfo>();
if (sharedInstances != null) {
TaskRequestManager taskManager = sharedInstances
.getTaskRequestManager();
CalendarRequestManager calManager = sharedInstances
.getCalendarRequestManager();
Vector<TaskEvent> tasks = null;
Vector<CalendarEvent> events = null;
if (taskManager != null)
tasks = taskManager.readTasksToday(mContext);
if (calManager != null)
events = calManager.readCalendarEventsToday(mContext);
if (!tasks.isEmpty())
mAllEvents.addAll(tasks);
if (!events.isEmpty())
mAllEvents.addAll(events);
mAllEvents = sortByDate(mAllEvents);
}
}
#SuppressWarnings({ "unchecked", "rawtypes" })
public Vector<EventInfo> sortByDate(Vector<EventInfo> events)
{
Vector<EventInfo> sortedEvents = new Vector<EventInfo>();
for(EventInfo event : events)
{
if ((event.getStartTime()+event.getEventDuration()) > GregorianCalendar.getInstance().getTimeInMillis())
sortedEvents.add(event);
}
Collections.sort(events, new Comparator() {
public int compare(Object arg0, Object arg1)
{
EventInfo event0 = (EventInfo)arg0;
EventInfo event1 = (EventInfo)arg1;
if (event0.getStartTime()+event0.getEventDuration() > event1.getStartTime()+event1.getEventDuration())
return 1;
else if (event0.getStartTime()+event0.getEventDuration() == event1.getStartTime()+event1.getEventDuration())
return 0;
else if (event0.getStartTime()+event0.getEventDuration() < event1.getStartTime()+event1.getEventDuration())
return -1;
return 0;
}
});
return sortedEvents;
}
#Override
public int getCount() {
return mAllEvents.size();
}
#Override
public long getItemId(int arg0) {
return (arg0);
}
#Override
public RemoteViews getLoadingView() {
return null;
}
#Override
public RemoteViews getViewAt(int position) {
// Getting item view
RemoteViews row = new RemoteViews(mContext.getPackageName(),
R.layout.done_task_item);
EventInfo eventInfo = mAllEvents.get(position);
row.setInt(R.id.item_event, "setBackgroundColor", Color.argb(60, Color.red(eventInfo.getColor()), Color.green(eventInfo.getColor()), Color.blue(eventInfo.getColor())));
// Converts startTime and endTime in string
String startTime = TimeCursor.getAdaptativeTime(eventInfo.getStartTime());
String endTime = TimeCursor.getAdaptativeTime((eventInfo
.getEventDuration() + eventInfo.getStartTime()));
//Get title
String title = eventInfo.getTitle();
// Setting data in the view
row.setTextViewText(R.id.titleTask, title);
row.setTextViewText(R.id.periodTask, startTime + " to " + endTime);
//Check type of event
if (eventInfo.isTask()) {
//endDate > GregorianCalendar.getInstance().getTimeInMillis() ) {
//Check if action exists
if (eventInfo.getAction() != null) {
//Get the action title
String action = eventInfo.getAction()
.getTitleText();
//Create a onClickPendingIntent for taskButton
PendingIntent onClickPendingIntent = null;
//Add related button
if (action.equals("Call"))
{
//Set call icon to taskButton
row.setImageViewResource(R.id.taskButton, R.drawable.ic_call_white );
//Get numbers from the contact
Vector<TelOrEmailItem> tel = eventInfo.getContact().getAllPhoneNumbers(mContext.getResources() , mContext, eventInfo.getAction());
// Creating an onclick event for call somebody
Intent callIntent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:"+tel.get(0).getMainText()));
onClickPendingIntent = PendingIntent.getActivity(
mContext, 0, callIntent, 0);
}
else if (action.equals("SMS"))
{
//Set sms icon to taskButton
row.setImageViewResource(R.id.taskButton, R.drawable.ic_sms_white);
//Get numbers from the contact
Vector<TelOrEmailItem> tel = eventInfo.getContact().getAllPhoneNumbers(mContext.getResources() , mContext, eventInfo.getAction());
// Creating an onclick event for call somebody
Intent smsIntent = new Intent(Intent.ACTION_SENDTO, Uri.parse("smsto:"+tel.get(0).getMainText()));
onClickPendingIntent = PendingIntent.getActivity(
mContext, 0, smsIntent, 0);
}
/*else if (action.equals("Chat with"))
row.setImageViewResource(R.id.taskButton, R.drawable.ic_chat_white);*/
else if (action.equals("eMail") || action.equals("Mail") || action.equals("Write to"))
{
//Set email icon to taskButton
row.setImageViewResource(R.id.taskButton, R.drawable.ic_email_white);
//Get numbers from the contact
Vector<TelOrEmailItem> tel = eventInfo.getContact().getAllEMails(mContext, eventInfo.getAction());
//Creating an onclick event for email somebody
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setType("plain/text");
emailIntent.putExtra(Intent.EXTRA_EMAIL,
new String[]{tel.get(0).getMainText()});
onClickPendingIntent = PendingIntent.getActivity(
mContext, 0, emailIntent, 0);
}
/*else if (action.equals("Skype"))
row.setImageViewResource(R.id.taskButton, R.drawable.ic_skype_white);*/
//Assign the intent to the taskButton
row.setOnClickPendingIntent(R.id.taskButton, onClickPendingIntent);
// Creating an onclick event for the done button
Intent doneIntent = new Intent(mContext, WidgetProvider.class);
doneIntent.putExtra("DONE_TASK", "DOOOOM");
PendingIntent onDoneIntent = PendingIntent.getActivity(mContext, 0, doneIntent, 0);
row.setOnClickPendingIntent(R.id.doneButton, onDoneIntent);
}
else
row.setViewVisibility(R.id.taskButton, View.GONE); //hidde the taskButton
return row;
}
//Check if it's an event
else if(eventInfo.isEvent()) {
//hidde task button (Done)
row.setViewVisibility(R.id.doneButton, View.GONE);
CalendarEvent ev = eventInfo.getCalendarEvent();
String location = ev.getEventLocation();
if (location != null && !location.isEmpty())
{
//Set the locate icon on the taskButton
row.setImageViewResource(R.id.taskButton, R.drawable.ic_locate_white);
//Define the place to open the map
Intent mapIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("geo:0,0?q="+location));
PendingIntent onMapIntent = PendingIntent.getActivity(
mContext, 0, mapIntent, 0);
row.setOnClickPendingIntent(R.id.taskButton, onMapIntent);
}
else
row.setViewVisibility(R.id.taskButton, View.GONE);
return row;
}
return null;
}
#Override
public int getViewTypeCount() {
return 1;
}
#Override
public boolean hasStableIds() {
return (true);
}
#Override
public void onCreate() {
}
#Override
public void onDataSetChanged() {
// On data changes, update mTasks
updateAllEventsVector();
}
#Override
public void onDestroy() {
mAllEvents = null;
}
}
And thank you :)
Edit:
Yeaye !
Problem solved or not...
Method onClickPendingIntent() is working now, here is the code :
// Creating an onclick event for the done button
Intent onClickDone = new Intent(mContext, DoneTaskActivity.class);
onClickDone.putExtra("TASK_ID", eventInfo.getTaskEvent().getTaskId());
PendingIntent onClickPendingDone = PendingIntent.getActivity(mContext, 0, onClickDone, 0);
row.setOnClickPendingIntent(R.id.doneButton, onClickPendingDone);
But another problem exists: The DoneTaskActivity doesn't receive the extra declared as TASK_ID. In the onCreate() method of the DoneTaskActivity, the Bundle var in parameter stays to null.
Help :(
When using a list view in a widget you need to use the setOnClickFillInIntent listener. From the official docs;
When using collections (eg. ListView, StackView etc.) in widgets, it
is very costly to set PendingIntents on the individual items, and is
hence not permitted. Instead a single PendingIntent template can be
set on the collection
Inside your RemoteViewsFactory you use it like this;
public RemoteViews getViewAt(int position) {
// position will always range from 0 to getCount() - 1.
// Construct a RemoteViews item based on the app widget item XML file, and set the
// text based on the position.
RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.widget_item);
rv.setTextViewText(R.id.widget_item, mWidgetItems.get(position).text);
// Next, set a fill-intent, which will be used to fill in the pending intent template
// that is set on the collection view in StackWidgetProvider.
Bundle extras = new Bundle();
extras.putInt(StackWidgetProvider.EXTRA_ITEM, position);
Intent fillInIntent = new Intent();
fillInIntent.putExtras(extras);
// Make it possible to distinguish the individual on-click
// action of a given item
rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent);
...
// Return the RemoteViews object.
return rv;
}
You also need to use a standard PendingIntent as individual list items can't set PendingIntents, see docs on AppWidgets.
The first parameter of 'putExtra' must include a package prefix.
If your app's package name is 'com.test', you should put 'com.test.TASK_ID'. Same name on receiving part.
try to add this:
// When intents are compared, the extras are ignored, so we need to embed the extras
// into the data so that the extras will not be ignored.
onClickDone.setData(Uri.parse(onClickDone.toUri(Intent.URI_INTENT_SCHEME)));
before
row.setOnClickPendingIntent(R.id.doneButton, onClickPendingDone);
Related
I have got a MainActivity which gets the current location on click of a button. In the activity, and the location is stored with three different methods in
SharedPreferences
An online SQL Database
In a text file on the device
I have another class to start a foreground service (ForegroundService.java) which starts with the click of another button in the MainAcivity and stops with a third button.
My plan is to have regular (1 hour interval) location updates using the ForegroundService and a JobService. So if we click on the StartService-button in the MainActivity the foreground service should start and regularly call the methods from the MainActivity: getCurrentGPSPosition (to get the location), and the three methods to store the information.
The MainActivity works fine. The ForegroundService can be started and stopped bud I do not know how to make it call the methods from the MainActivity.
Here is the code -
ForegroundService.java:
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import static com.example.currentlocation.App.CHANNEL_ID;
public class ForegroundService extends Service {
#Override
public void onCreate() {
super.onCreate();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
//String input = intent.getStringExtra("inputExtra");
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("GPS position tracker")
//.setContentText(input)
.setSmallIcon(R.drawable.ic_baseline_gps_fixed_24)
.setContentIntent(pendingIntent)
.build();
startForeground(1, notification);
return START_NOT_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
App.java
import android.app.Application;
import android.app.NotificationChannel;
import android.app.NotificationManager;
public class App extends Application {
public static final String CHANNEL_ID = "CurrentLocationServiceChannel";
#Override
public void onCreate() {
super.onCreate();
createNotificationChannel();
}
private void createNotificationChannel(){
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationChannel serviceChannel = new NotificationChannel(
CHANNEL_ID,
"Current Location Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(serviceChannel);
}
}
}
In the MainActivity the following parts:
public class MainActivity extends AppCompatActivity {
........
//Backgroundservice
public void startService(View v){
// String input = editTextInput.getText().toString();
Intent serviceIntent = new Intent(this, ForegroundService.class);
// serviceIntent.putExtra("inputExtra", input);
startService(serviceIntent);
}
public void stopService(View v){
Intent serviceIntent = new Intent(this, ForegroundService.class);
stopService(serviceIntent);
}
//Method to store in SQL online
public void OnReg() {.......code....}
//Method to save data in SharedPreferences
public void saveData() {.......code....}
//Method to save data in Internal File
public void saveToFile() {.......code....}
//method for GPS request
getCurrentGPSLocation() {.......code....}
}
Has anybody got an idea how to work that out? Or do you need more details? Thanks for your help!
Don't put those functions in MainActivity. If you need them to be called from both the Activity and a Service, put them in a separate class that can be instantiated as needed. If you can't do that because there's data in MainActivity that both need, rethink your architecture- that data won't be available when the job service fires anyway.
OK, first solution is to make a new class (Functions.java) and using an intent to call this class form the MainActivity and then do the location request there and then give the data back to the MainActivity.
Second step would then be to do the same from the foreground service.
The Problem is, the functions class does not get the data from the method. If I put hard-coded strings into the intent, they are transmitted to the MainActivity, so the intent works. So this is only part of the full solution.
Here's the code:
public class Functions extends AppCompatActivity {
//initialize variable
FusedLocationProviderClient fusedLocationProviderClient;
private double ser_Latitude;
private double ser_Longitude;
private String ser_Accuracy;
private String ser_Altitude;
private String currentDateandTime;
private String ser_Location;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Boolean precision = getIntent().getBooleanExtra("precision", true);
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(
Functions.this);
getCurrentGPSLocation();
Intent intent = new Intent();
intent.putExtra("latitude", ser_Latitude);
intent.putExtra("longitude", ser_Longitude);
intent.putExtra("accuracy", ser_Accuracy);
intent.putExtra("altitude", ser_Altitude);
intent.putExtra("location", ser_Location);
intent.putExtra("currentDateandTime", currentDateandTime);
setResult(RESULT_OK, intent);
Functions.this.finish();
}
//Force new GPS Location Request
private void getCurrentGPSLocation() {
// get the new location from the fused client
// update the UI - i.e. set all properties in their associated text view items
//Initialize new location request
LocationRequest locationRequest = new LocationRequest()
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
.setInterval(3000)
.setFastestInterval(2000)
.setNumUpdates(1)
;
//Initialize location call back
LocationCallback locationCallback = new LocationCallback() {
#Override
public void onLocationResult(LocationResult locationResult) {
//Initialize location1
Location location1 = locationResult.getLastLocation();
//Set latitude
ser_Latitude = location1.getLatitude();
//Set longitude
ser_Longitude = location1.getLongitude();
//Set Accuracy
double ser_accura1 = location1.getAccuracy();
ser_Accuracy = new DecimalFormat("##.##").format(ser_accura1) + " m";
//Set Altitude
double ser_altit1 = location1.getAltitude();
ser_Altitude = new DecimalFormat("##.##").format(ser_altit1) + " m";
//Get Adress
/* Geocoder geocoder = new Geocoder(getApplicationContext(), Locale.getDefault());
try {
List<Address> listAddresses = geocoder.getFromLocation(ser_Latitude, ser_Longitude, 1);
if (null != listAddresses && listAddresses.size() > 0) {
String _Location1 = listAddresses.get(0).getAddressLine(0);
//Set Location
//ser_Location = String.valueOf(_Location1);
}
} catch (IOException e) {
e.printStackTrace();
}*/
//Set location as hard-coded string for testing
ser_Location = "London";
//Set Update Time
SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy 'um ' HH:mm:ss z");
currentDateandTime = sdf.format(new Date());
}
};
//Request location updates
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
fusedLocationProviderClient.requestLocationUpdates(locationRequest
, locationCallback, Looper.myLooper());
}
}
The new GPS request is triggered as I can see running the app, but no information is delivered from the method getCurrentGPSLocation() to the intent, which I can see because even the hard-coded location string is not delivered.
This needs revision please.
Good day.
I'm trying to create a widget that will show the balance on the screen.
Created 2 buttons and set up the listener to determine which button was pressed.
Difficulty:
How to update data by clicking on the button or reload the widget so that the data is updated?
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.RemoteViews;
import android.widget.Toast;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.DateFormat;
import java.util.Date;
public class EthWidgetProvider extends AppWidgetProvider {
private static final String SYNC_CLICKED = "automaticWidgetSyncButtonClick";
private static final String SYNC_CLICKED2 = "automaticWidgetSyncButtonClick2";
static void updateAppWidget(final Context context, final AppWidgetManager appWidgetManager, final int appWidgetId) {
RequestSingleton.getInstance(context).fetchData(new VolleyCallback() {
#Override
public void onSuccessRequest(JSONArray result) {
Log.i("Response", result.toString());
String price = "";
try {
JSONObject etherObject = result.getJSONObject(0);
price = etherObject.getString("price_usd");
} catch (JSONException e) {
Log.e("JSONException", e.toString());
}
Log.i("Price", price);
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.example_widget);
views.setTextViewText(R.id.gprs, "$" + price);
String currentDateTimeString = DateFormat.getDateTimeInstance().format(new Date());
views.setTextViewText(R.id.timeofday, currentDateTimeString);
appWidgetManager.updateAppWidget(appWidgetId, views);
}
});
}
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
RemoteViews remoteViews;
ComponentName watchWidget;
remoteViews = new RemoteViews(context.getPackageName(), R.layout.example_widget);
watchWidget = new ComponentName(context, EthWidgetProvider.class);
remoteViews.setOnClickPendingIntent(R.id.refresh, getPendingSelfIntent(context, SYNC_CLICKED));
remoteViews.setOnClickPendingIntent(R.id.example_widget_button, getPendingSelfIntent(context, SYNC_CLICKED2));
appWidgetManager.updateAppWidget(watchWidget, remoteViews);
for(int appId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appId);
}
}
#Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
super.onReceive(context, intent);
if (SYNC_CLICKED.equals(intent.getAction())) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
RemoteViews remoteViews;
ComponentName watchWidget;
remoteViews = new RemoteViews(context.getPackageName(), R.layout.example_widget);
watchWidget = new ComponentName(context, EthWidgetProvider.class);
remoteViews.setTextViewText(R.id.timeofday, "TESTING");
appWidgetManager.updateAppWidget(watchWidget, remoteViews);
}
if (SYNC_CLICKED2.equals(intent.getAction())) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
RemoteViews remoteViews;
ComponentName watchWidget;
remoteViews = new RemoteViews(context.getPackageName(), R.layout.example_widget);
watchWidget = new ComponentName(context, EthWidgetProvider.class);
remoteViews.setTextViewText(R.id.timeofday, "TESTING2");
appWidgetManager.updateAppWidget(watchWidget, remoteViews);
}
}
protected PendingIntent getPendingSelfIntent(Context context, String action) {
Intent intent = new Intent(context, getClass());
intent.setAction(action);
return PendingIntent.getBroadcast(context, 0, intent, 0);
}
}
By pressing button 1, I need to show the activity. By pressing the second button, you need to update the widget so that the data is updated, that is, so that the request to the server goes again and the data is updated.
Any help would be appreciated.
I don't get you idea with the Broadcast.
The broadcast should not be inner class
Broadcast should extend BroadcastReceiver class
Broadcast should be defined in manifest
So when you do this, your receiver should be called after buttons are clicked.
But, you want something to wake up your Widget. And then Widget#onUpdate will fetch the updated data (that you download on widget click) and show them.
To request update of your widget, you can use something like this:
public static void updateAppWidget(#NonNull Context applicationContext,
#NonNull Class<? extends AppWidgetProvider> widgetProviderClass) {
final ComponentName component = new ComponentName(applicationContext, widgetProviderClass);
final int[] widgetIds;
try {
widgetIds = AppWidgetManager.getInstance(applicationContext).getAppWidgetIds(component);
} catch (RuntimeException re) {
Log.d(re, "Unable to obtain widget IDs.");
return;
}
if (widgetIds.length == 0) {
Log.d("There is no widget to be updated.");
return;
}
final Intent intent = new Intent(applicationContext, widgetProviderClass);
intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, widgetIds);
applicationContext.sendBroadcast(intent);
}
So to sum it up:
In widget setup views, listeners, and populate with last known data.
In listener broadcast either open Activity, or run some download
Don't forget BroadcastReceivers are running on UI thread; use
goAsync() approach.
The downloaded data should be stored to some place, where widget can
obtain them
call mentioned function to trigger widget update.
I believe, this is a quite specific and complex topic unlike the rest of Android, so feel free to ask anything I might missed :)
There is a very helpful must-read official guide titled "Build an App Widget":
(https://developer.android.com/guide/topics/appwidgets)
Please read this page carefully so you understand the concepts and focus on the AppWidgetProvider section for your problem. Although in this section, they use: PendingIntent.getActivity(), you are supposed to use a Service as PendingIntent.getService() to have your Service called on button click, and then have the Service update your Widget.
Basically you'll see that you need to:
Create a Service
Create a PendingIntent:
Intent intent = new Intent(context, YourService.class);
PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);
Set the onClickPendingIntent listener on your RemoteViews object like this:
views.setOnClickPendingIntent(R.id.button, pendingIntent);
To call activity:
remoteViews.setOnClickPendingIntent( R.id.button1, PendingIntent.getActivity(context, 0, new Intent( context, Main.class), 0) );
To update widgets from another class :
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance( context );
int ids[] = appWidgetManager.getAppWidgetIds( componentName );
for( int id : ids ){
//update textView here
appWidgetManager.updateAppWidget( id, views);
}
Hello friends i am creating mp3 player application my application is successfully build i want implement click event notification when user goes on background they control media from notification panel my notification also build successfully but problem is that how control music and change image on notification .addaction() when user click on pause the image change to play and when song is play image back change to pause and media player is also and i am also want get songs title, and artist here my code you can easily understand!
public void play(int songindex) {
song = songList.get(songindex);
try {
if (mediaPlayer != null) {
mediaPlayer.release();
mediaPlayer = null;
}
Uri uri = Uri.parse("file:///" + song.getGetpath());
mediaPlayer = MediaPlayer.create(mContext, uri);
title.setText(song.getTitle());
artist.setText(song.getArtist());
notificationTitleText=title.getText();
notificationDescText=artist.getText();
handler = VisualizerDbmHandler.Factory.newVisualizerHandler(getApplicationContext(), mediaPlayer);
audioVisualization.linkTo(handler);
mediaPlayer.start();
seekBar.setProgress(0);
seekBar.setMax(100);
updateProgressBar();
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
play.setVisibility(View.GONE);
pause.setVisibility(View.VISIBLE);
play_main.setVisibility(View.GONE);
pause_main.setVisibility(View.VISIBLE);
Animation aniRotate = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.rotate);
rotate.startAnimation(aniRotate);
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
if (checked) {
mediaPlayer.setLooping(true);
mediaPlayer.start();
} else if (isShuffle) {
// shuffle is on - play a random song
Random rand = new Random();
currentSongIndex = rand.nextInt((songList.size() - 1) - 0 + 1) + 0;
play(currentSongIndex);
} else {
// no repeat or shuffle ON - play next song
if (currentSongIndex < (songList.size() - 1)) {
play(currentSongIndex + 1);
currentSongIndex = currentSongIndex + 1;
} else {
// play first song
play(0);
currentSongIndex = 0;
}
}
}
});
}
} catch (Exception e) {
Toast.makeText(getApplicationContext(), "" + e, Toast.LENGTH_SHORT).show();
}
}
public void shownotification(){
Bitmap largeImage = BitmapFactory.decodeResource(getResources(),R.drawable.dog);
Notification channel = new NotificationCompat.Builder(getApplicationContext(),CHANNEL_ID_1)
.setSmallIcon(R.drawable.ic_music)
.setContentTitle(notificationTitleText
)
.setContentText(notificationDescText)
.setLargeIcon(largeImage)
.addAction(R.drawable.ic_like,"like",null)
.addAction(R.drawable.ic_prev,"prev",null)
.addAction(R.drawable.ic_pause,"pause",null)
.addAction(R.drawable.ic_next,"next",null)
.addAction(R.drawable.ic_dislike,"dislike",null)
.setStyle(new android.support.v4.media.app.NotificationCompat.MediaStyle().
setShowActionsInCompactView(1,2,3))
.build();
mNotificationManagerCompat.notify(1,channel);
}
gettext()method is working fine but it work on first when any clicked event is happen if song play oncomplete and next song is not get text value
I am assuming that you are playing songs from an Activity but this will also work for a service.
Put this in your activity or service
private final BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(action.equals("com.mypackage.ACTION_PAUSE_MUSIC")){
//Do whatever you want. Ex. Pause
}
//Similarly this can be done for all actions
}};
Make your show notification method like this
public void shownotification(){
Bitmap largeImage = BitmapFactory.decodeResource(getResources(),R.drawable.dog);
Intent pauseIntent = new Intent("com.mypackage.ACTION_PAUSE_MUSIC");
PendingIntent pausePendingIntent = PendingIntent.getBroadcast(this, 1, pauseIntent, 0);
// Similarly you can create an intent and pending intent pair for each action you want just change the string in intent constructor
Notification channel = new NotificationCompat.Builder(getApplicationContext(),CHANNEL_ID_1)
.setSmallIcon(R.drawable.ic_music)
.setContentTitle(notificationTitleText
)
.setContentText(notificationDescText)
.setLargeIcon(largeImage)
.addAction(R.drawable.ic_like,"like",null)
.addAction(R.drawable.ic_prev,"prev",null)
.addAction(R.drawable.ic_pause,"pause",pausePendingIntent) //like this attach every action with respective pending intent
.addAction(R.drawable.ic_next,"next",null)
.addAction(R.drawable.ic_dislike,"dislike",null)
.setStyle(new android.support.v4.media.app.NotificationCompat.MediaStyle()
.setShowActionsInCompactView(1,2,3))
.build();
mNotificationManagerCompat.notify(1,channel);}
I want to add one more thing to #Kumar Manas answer
i.e We need to register reciever that is being created in activity.
private final BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(action.equals("com.mypackage.ACTION_PAUSE_MUSIC")){
//Do whatever you want. Ex. Pause
}
//Similarly this can be done for all actions
}};
To register your Reciever add these lines in onCreate()
IntentFilter intentFilter=new IntentFilter();
intentFilter.addAction("com.mypackage.ACTION_PAUSE_MUSIC");
registerReceiver(receiver,intentFilter);
Note: You can add as many actions you want
You may get your answer by following this tutorial "Android Music Player Controls on Lock Screen and Notifications"
I am writing an application widget that takes data from a server and displays them in a appwidget.
The problem is that when there is no internet connection and at this point the system updates the widget, TextView text value reset to the default text setted with android:text="sometext"
It happens like this:
Widget placed on homescreen
Internet connection is active
Widget successfully updated
The text of the response from the server is installed in the TextView
Internet connection is not active
The system updates the widget
Previous text in the TextView reset to the value setted in android:text=""
I know that somewhere I incorrectly do something, because in other widgets (not my) with no connection to the Internet does not reset.
File WidgetProvider.java
public class WidgetProvider extends AppWidgetProvider {
public static String LOG_TAG = "MYAPPLOG";
#Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
Log.d(LOG_TAG, "onReceive");
}
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
Log.d(LOG_TAG, "onUpdate");
for (int widgetID : appWidgetIds)
{
updateWidget(context, widgetID);
}
}
#Override
public void onDeleted(Context context, int[] appWidgetIds) {
super.onDeleted(context, appWidgetIds);
Log.d(LOG_TAG, "onDeleted");
}
public void updateWidget(Context context, int widgetID)
{
context.startService(new Intent(context, UpdatingService.class).putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetID));
}
}
File UpdatingService.java
public class UpdatingService extends IntentService {
public static String LOG_TAG = "MYAPPLOG";
public UpdatingService() {
super("UpdatingService");
}
#Override
protected void onHandleIntent(Intent intent) {
// getting widgetID from intent and other vars
RemoteViews remoteViews = new RemoteViews(getApplicationContext().getPackageName(),
R.layout.initial_layout);
if(isConnected(getApplicationContext()))
{
String response = getServerResponse();
if(response != null)
{
try {
JSONObject JSON = new JSONObject(response);
// get data from server
// ...
// set values to the views
remoteViews.setTextViewText(R.id.textView, someText);
} catch (JSONException e) {
e.printStackTrace();
Log.d(LOG_TAG, "JSONObject failed");
}
}
else
{
// LOG: error connection to server
}
}
else
{
// LOG: No internet connection
}
// updating apwidget (set click action for the some button)
// if not do update then button will not work
Intent someIntent = new Intent(getApplicationContext(), WidgetProvider.class);
someIntent.setAction(WidgetProvider.ACTION_GOTO);
PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(),
widgetID, someIntent, PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.goToLayout, pendingIntent);
AppWidgetManager.getInstance(getApplicationContext().getApplicationContext())
.updateAppWidget(widgetID, remoteViews);
}
public boolean isConnected(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo ni = cm.getActiveNetworkInfo();
if (ni != null && ni.isConnected()) {
return true;
}
return false;
}
public String getServerResponse() {
// using HttpURLConnection
}
}
I hope for your help or a little tip. I wrote several widgets and all with this problem. Thank you very much for your attention.
When an AppWidget update happens then every property must be set, like all the values of text views, all click listeners, colors, etc. Simply everything. What is not set will use the default value from the layout XML.
In your case you do nothing in the else branch ("no internet") thus your app widget ends up with the default text. So when you fetch the data from the server you must save it and use it the next time there is no internet connection.
I know this has been asked for many times but I went through the documentation from top to bottom, read all answers here and none of them helped.
To be honest, each answer says something different about how to aproach this.
Now back to my question. I want to update the widget list view from some activity and I created WidgetProvider#sendUpdateBroadcastToAllWidgets() for this purpose which I call from the activity.
It eventually calls the onUpdate() so the broadcast is received correctly. But the views are not refreshed.
I also tried to call AppWidgetManager#notifyAppWidgetViewDataChanged() and refreshed the data in WidgetFactory#onDataSetChanged() but the method has never been called.
So I guess this all does not work because the remote views factory is cached but I don't know how to reliably overcome this. Any thoughts?
And what about contexts? I always have to supply one but I don't really care much which one. Does it matter?
Thanks
PROVIDER
public class WidgetProvider extends AppWidgetProvider {
public static void sendUpdateBroadcastToAllWidgets(Context context) {
int allWidgetIds[] = AppWidgetManager.getInstance(context).getAppWidgetIds(new ComponentName(context, WidgetProvider.class));
Intent intent = new Intent(context, WidgetProvider.class);
intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, allWidgetIds);
context.sendBroadcast(intent);
}
#Override
public void onUpdate(Context context, AppWidgetManager widgetManager, int[] widgetIds) {
for (int id : widgetIds) {
updateWidget(context, widgetManager, id);
}
super.onUpdate(context, widgetManager, widgetIds);
}
#Override
public void onDeleted(Context context, int[] widgetIds) {
WidgetPreferences prefs = new WidgetPreferences(context);
for (int widgetId : widgetIds) {
prefs.getWidgetPreferences(widgetId).edit().clear().commit();
}
super.onDeleted(context, widgetIds);
}
private static void updateWidget(Context context, AppWidgetManager widgetManager, int widgetId) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget);
// set list adapter
Intent serviceIntent = new Intent(context, WidgetService.class);
serviceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
serviceIntent.setData(Uri.parse(serviceIntent.toUri(Intent.URI_INTENT_SCHEME)));
views.setRemoteAdapter(android.R.id.list, serviceIntent);
views.setEmptyView(android.R.id.list, android.R.id.empty);
// set widget title
WidgetDataCategory category = new WidgetPreferences(context).getSavedCategory(widgetId);
views.setTextViewText(R.id.titleText, context.getString(category.titleResourceId()));
// set onclick listener - we create a pending intent template and when an items is clicked
// the intent is filled with missing data and sent
Intent startActivityIntent = new Intent(context, SimplePersonDetailActivity.class);
startActivityIntent.setData(Uri.parse(startActivityIntent.toUri(Intent.URI_INTENT_SCHEME)));
startActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivityIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, startActivityIntent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setPendingIntentTemplate(android.R.id.list, pendingIntent);
// all hail to Google
widgetManager.updateAppWidget(widgetId, views);
}
}
FACTORY
public class WidgetFactory implements RemoteViewsService.RemoteViewsFactory {
private Context context;
private List<Person> people = new ArrayList<>();
public WidgetFactory(Context context, Intent intent) {
this.context = context;
int widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
if (widgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
WidgetPreferences prefs = new WidgetPreferences(context);
WidgetDataCategory category = prefs.getSavedCategory(widgetId);
int numberOfItemsToShow = prefs.getSavedLimit(widgetId);
people = category.filterAndSlice(new PersonDao(context).getAllForGroup(Constants.SIMPLE_GROUP_ID), numberOfItemsToShow);
}
}
#Override
public void onCreate() {}
#Override
public void onDataSetChanged() {}
#Override
public void onDestroy() {}
#Override
public int getCount() {
return people.size();
}
#Override
public RemoteViews getViewAt(int position) {
Person person = people.get(position);
BigDecimal amount = ListViewUtil.sumTransactions(new TransactionDao(context).getAllForPerson(person));
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.item_widget);
remoteViews.setTextViewText(R.id.nameText, person.getName());
remoteViews.setTextViewText(R.id.amountText, MoneyFormatter.withoutPlusPrefix().format(amount));
// fill details for the onclick listener (updating the pending intent template
// set in the WidgetProvider)
Intent listenerIntent = new Intent();
listenerIntent.putExtra(Constants.PERSON_ID, people.get(position).getId());
remoteViews.setOnClickFillInIntent(R.id.widgetItem, listenerIntent);
return remoteViews;
}
#Override
public RemoteViews getLoadingView() {
return null;
}
#Override
public int getViewTypeCount() {
return 1;
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public boolean hasStableIds() {
return true;
}
}
I would say that notifyAppWidgetViewDataChanged method should work for you.
You have to build AppWidgetManager and get appWidgetIds and then just call notifyAppWidgetViewDataChanged on your AppWidgetManager.
Pseudo Code,
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int appWidgetIds[] = appWidgetManager.getAppWidgetIds(
new ComponentName(context, WidgetProvider.class));
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.listview);
For more, you can checkout my answer here which contains demo on github.
I had a widget implementation in my project. I have modified the below code so that data in widget can be changed from one of my Activity in application. Only showing the essential code for your specific use case. Here I am having a button with text in my widget. Through login button click in my Activity , I am modifying the button text in my widget
Below is my AppWidgetProvider.java class
public class AppWidgetTrackAsset extends AppWidgetProvider{
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// Perform this loop procedure for each App Widget that belongs to this provider
final int N = appWidgetIds.length;
for (int i=0; i<N; i++) {
int appWidgetId = appWidgetIds[i];
// Create an Intent to launch Activity
Intent intent = new Intent(context, WidgetAlertActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
// Get the layout for the App Widget and attach an on-click listener
// to the button
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.app_widget_track_asset);
views.setOnClickPendingIntent(R.id.sosButton, pendingIntent);
// Tell the AppWidgetManager to perform an update on the current app widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
#Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
Log.v(Constants.WIDGET_LOG, "onReceive called with " + intent.getAction());
if (intent.getAction().equals("update_widget")) {
// widget update started
RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
R.layout.app_widget_track_asset);
// Update text , images etc
remoteViews.setTextViewText(R.id.sosButton, "My updated text");
// Trigger widget layout update
AppWidgetManager.getInstance(context).updateAppWidget(
new ComponentName(context, AppWidgetTrackAsset.class), remoteViews);
}
}
#Override
public void onEnabled(Context context) {
// Enter relevant functionality for when the first widget is created
}
#Override
public void onDisabled(Context context) {
// Enter relevant functionality for when the last widget is disabled
}
}
Below is my Activity where I am updating the widget button text on click of my login button
LoginActivity.java
public class LoginActivity extends AppCompatActivity implements View.OnClickListener{
Button loginButton;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
loginButton=(Button)findViewById(R.id.loginButton);
loginButton.setOnClickListener(this);
}
#Override
public void onClick(View v) {
if(v.getId() == R.id.loginButton){
updateWidget();
}
}
private void updateWidget(){
try {
Intent updateWidget = new Intent(this, AppWidgetTrackAsset.class);
updateWidget.setAction("update_widget");
PendingIntent pending = PendingIntent.getBroadcast(this, 0, updateWidget, PendingIntent.FLAG_CANCEL_CURRENT);
pending.send();
} catch (PendingIntent.CanceledException e) {
Log.e(Constants.UI_LOG,"Error widgetTrial()="+e.toString());
}
}
}
Layout for app widget goes like this
app_widget_track_asset.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/widgetbackground"
android:padding="#dimen/widget_margin">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/sosButton"
android:text="SOS"
android:textSize="20sp"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
/>
</RelativeLayout>
Below is the manifest file essential part for widget
<receiver android:name=".appwidget.AppWidgetTrackAsset">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="#xml/app_widget_track_asset_info" />
</receiver>