Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android: How to RESUME Fragment class from Notification. NOT create new instance

I want to start a CountDownTimer in a Fragment class, and when the timer hits, say 3 minutes, the fragment class (though the app is in the background) will create a Notification. When the user pushes the notification, the app holding this fragment with the CountDownTimer, should resume to its "state" as if it was re-opened from the home-screen. So when the user hits the "back-button" the user should be taken to the the previous Activity in the app, and not back to the home screen.

My Fragment_C.java class, which is inside an Activity_C.java class:

package example.app;

import java.text.SimpleDateFormat;
import java.util.TimeZone;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.support.v4.app.Fragment;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.telephony.SmsManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;

public class Fragment_C extends Fragment {

    static int amount;
    static String phone;
    static String message;

    long mMilliseconds;
    SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat("HH:mm:ss");
    TextView mTextView;

    CountDownTimer mCountDownTimer = new CountDownTimer(amount * 60000, 1000) {
        @Override
        public void onFinish() {
            mTextView.setText(mSimpleDateFormat.format(0));
                        // Do something cool
        }

        public void onTick(long millisUntilFinished) {
                        // Show the countdown in a TextView:
            mTextView.setText(mSimpleDateFormat.format(millisUntilFinished));

                        // Get the time in string format, to use for if-statements:
            String millisInString = mSimpleDateFormat.format(millisUntilFinished);

            if (millisInString.equals("00:03:00")) {
                generateNotification();
                Log.i("There is exactly 3 minutes left", "YO!");
            }
            if (millisInString.equals("00:02:00")) {
                generateNotification();
                Log.i("There is exactly 2 minutes left", "YO!");
            }
            if (millisInString.equals("00:01:00")) {
                generateNotification();
                Log.i("There is exactly 1 minute left", "YO!");
            }

        }
    };

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        // Get the view for this fragment, to retrieve views from it.
        View view = inflater.inflate(R.layout.fragment_c, container, false);

        mSimpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
        mTextView = (TextView) view.findViewById(R.id.timerview);

        mCountDownTimer.start();

        // Get the start_alarm_button object from the fragment_c.xml:
        Button button = (Button) view
                .findViewById(R.id.fragment_c_cancel_alarm_button);
        button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                // If this button is pushed, return to Activity_B:
                mCountDownTimer.cancel();
                                // Basically push the "back-button"
                getActivity().finish();
            }
        });

        return view;
    }

    @Override
    public void onDestroy(){
        super.onDestroy();
                // Stop the CountDownTimer if the Activity gets destroyed:
        if(mCountDownTimer != null){
            mCountDownTimer.cancel();
        }
    }

    public static void setTimer(int min) {
        amount = min;
    }

    public static void setMessage(String msg) {
        message = msg;
    }

    public static void setNumber(String num) {
        phone = num;
    }

    public void generateNotification() {

                // This basically taken from developer.android.com
        NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
                getActivity()).setSmallIcon(R.drawable.ic_launcher)
                .setContentTitle("Rescue Me ALARM")
                .setContentText("Press here to cancel the SOS SMS");

        // Make the notification play the default notification sound:
        Uri alarmSound = RingtoneManager
                .getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        mBuilder.setSound(alarmSound);

        // Creates an explicit intent for an Activity in your app
        Intent resultIntent = new Intent(getActivity(), Activity_C.class);

        // The stack builder object will contain an artificial back stack for the started  
                // Activity.
        // This ensures that navigating backward from the Activity leads out of
        // your application to the Home screen.
        TaskStackBuilder stackBuilder = TaskStackBuilder.create(getActivity());

        // Adds the back stack for the Intent (but not the Intent itself)
        stackBuilder.addParentStack(Activity_C.class);

        // Adds the Intent that starts the Activity to the top of the stack
        stackBuilder.addNextIntent(resultIntent);

        PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,
                PendingIntent.FLAG_UPDATE_CURRENT);

        mBuilder.setContentIntent(resultPendingIntent);
        NotificationManager mNotificationManager = (NotificationManager) getActivity()
                .getSystemService(Context.NOTIFICATION_SERVICE);

        // mId allows you to update the notification later on.
        mNotificationManager.notify(0, mBuilder.build());
    }
}

Here's the fragment_c.xml file:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/fragment_c_cancel_alarm_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/fragment_c_cancel_alarm_button" />

</LinearLayout>

Here's the Activity_C.java class:

package example.app;

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.view.Menu;

public class Activity_C extends FragmentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_c);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.activity__c, menu);
        return true;
    }

}

Here's the activity_c.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:baselineAligned="false">

    <fragment android:name="app.me.rescue.rescuemeapp.Fragment_C"
            android:id="@+id/fragment_c" 
            android:layout_weight="1"
            android:layout_width="0dp" 
            android:layout_height="match_parent" />

 </LinearLayout>

Here's the AndroidManifest.xml file:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="app.me.rescue.rescuemeapp"
    android:versionCode="1"
    android:versionName="1.0"
    android:launchMode="singleInstance" >

    <uses-sdk
        android:minSdkVersion="10"
        android:targetSdkVersion="19" />

    <uses-permission android:name="android.permission.SEND_SMS" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="app.me.rescue.rescuemeapp.Activity_A"
            android:label="@string/app_name"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="app.me.rescue.rescuemeapp.Activity_B"
            android:label="@string/title_activity_activity__b"
            android:screenOrientation="portrait">
        </activity>
        <activity
            android:name="app.me.rescue.rescuemeapp.Activity_C"
            android:label="@string/title_activity_activity__c"
            android:screenOrientation="portrait"
            android:launchMode="singleTask">

        </activity>
    </application>

</manifest>

I have tried some other solutions like resuming an activity from a notification or Android: How to resume an App from a Notification?, but with no luck, also because no one seem to use Fragments anywhere :) I need to though.

Any help is VERY welcome :) I'm a noob, so if you could make your answers easy to understand, it would be much appreciated :)

//////////////// ////////////////

EDIT 2:

I'm embarrassed by this, but I got it to work, but I don't really know how or why. I tried implementing different solutions, but in the end the part missing were this example's alteration of PendingIntent.getActivity()'s second argument: android pending intent notification problem (See the second answer by U-Ramos)

the changes I made are solely in the AndroidManifest.xml file and the "generateNotification()"-method in the Fragment_C.java class:

Fragment.java:

public void generateNotification() {
        NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
                getActivity()).setSmallIcon(R.drawable.ic_launcher)
                .setContentTitle("Rescue Me ALARM")
                .setContentText("Press here to cancel the SOS SMS");

        // Make the notification play the default notification sound:
        Uri alarmSound = RingtoneManager
                .getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        mBuilder.setSound(alarmSound);
        mBuilder.setOngoing(true);

        // Creates an explicit intent for an Activity in your app
        Intent resultIntent = new Intent(getActivity(), Activity_C.class);

        // This somehow makes sure, there is only 1 CountDownTimer going if the notification is pressed:
        resultIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);

        // The stack builder object will contain an artificial back stack for the started Activity.
        // This ensures that navigating backward from the Activity leads out of
        // your application to the Home screen.
        TaskStackBuilder stackBuilder = TaskStackBuilder.create(getActivity());

        // Adds the back stack for the Intent (but not the Intent itself)
        stackBuilder.addParentStack(Activity_C.class);

        // Adds the Intent that starts the Activity to the top of the stack
        stackBuilder.addNextIntent(resultIntent);

        // Make this unique ID to make sure there is not generated just a brand new intent with new extra values:
        int requestID = (int) System.currentTimeMillis();

        // Pass the unique ID to the resultPendingIntent:
        PendingIntent resultPendingIntent = PendingIntent.getActivity(getActivity(), requestID, resultIntent, 0);

        mBuilder.setContentIntent(resultPendingIntent);
        NotificationManager mNotificationManager = (NotificationManager) getActivity()
                .getSystemService(Context.NOTIFICATION_SERVICE);

        // mId allows you to update the notification later on.
        mNotificationManager.notify(0, mBuilder.build());
    }

AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="app.me.rescue.rescuemeapp"
    android:versionCode="1"
    android:versionName="1.0"
    android:launchMode="singleInstance" >

    <uses-sdk
        android:minSdkVersion="10"
        android:targetSdkVersion="19" />


    <uses-permission android:name="android.permission.SEND_SMS" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="app.me.rescue.rescuemeapp.Activity_A"
            android:label="@string/app_name"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="app.me.rescue.rescuemeapp.Activity_B"
            android:label="@string/title_activity_activity__b"
            android:screenOrientation="portrait">
        </activity>
        <activity
            android:name="app.me.rescue.rescuemeapp.Activity_C"
            android:parentActivityName="app.me.rescue.rescuemeapp.Activity_B"
            android:label="@string/title_activity_activity__c"
            android:screenOrientation="portrait">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="app.me.rescue.rescuemeapp.Activity_B" />            
        </activity>
    </application>

</manifest>

If anyone wants to leave a comment or a reason for why this does the trick, you are most welcome to do so :)

Happy Coding!

//////////////// ////////////////

EDIT 3:

Also! if you want the notification to go away, when the user is pressing it, simply put in the following code beneath the instantiation of the mBuider object:

mBuilder.setAutoCancel(true);

So that's that !

like image 976
NooberMan Avatar asked Dec 12 '13 10:12

NooberMan


1 Answers

In my opinion the first thing you should always do is overwrite onSaveInstanceState and onRestoreInstanceState in your activity so you can save the state of the activity once is stopped and set it back once is resumed again.

Having done that you can do several things, the bad one is to save the activity that triggered this one from the incoming intent and override onBackPressed to go back to the previous activity.

The better and easier one is just add resultIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); as stated here

like image 143
Caye Avatar answered Sep 29 '22 22:09

Caye