Java tutorial
/* * Copyright 2016 Niklas Schelten * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.raspi.chatapp.ui.chatting; import android.app.Activity; import android.app.NotificationManager; import android.app.ProgressDialog; import android.content.ClipData; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.preference.PreferenceManager; import android.provider.MediaStore; import android.support.v4.app.FragmentManager; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import com.alexbbb.uploadservice.UploadService; import com.raspi.chatapp.BuildConfig; import com.raspi.chatapp.R; import com.raspi.chatapp.ui.password.PasswordActivity; import com.raspi.chatapp.ui.settings.SettingsActivity; import com.raspi.chatapp.ui.util.emojicon.EmojiconEditText; import com.raspi.chatapp.util.Constants; import com.raspi.chatapp.util.Notification; import com.raspi.chatapp.util.internet.XmppManager; import com.raspi.chatapp.util.service.MessageService; import com.raspi.chatapp.util.storage.AndroidDatabaseManager; import com.raspi.chatapp.util.storage.MessageHistory; import com.raspi.chatapp.util.storage.file.MyFileUtils; import org.jivesoftware.smack.roster.RosterEntry; import java.io.BufferedInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.Date; /** * This activity is the main Activity which is being started and contains the * {@link ChatListFragment}, the {@link ChatFragment} and the {@link * SendImageFragment}.<br> * Here is the backstack managed and the callbacks from the fragments are * implemented. */ public class ChatActivity extends AppCompatActivity implements FragmentManager.OnBackStackChangedListener, ChatListFragment.OnFragmentInteractionListener, ChatFragment.OnChatFragmentInteractionListener, SendImageFragment.OnFragmentInteractionListener { /** * The requestCode for accessing the library */ private static final int SEND_LIBRARY_IMAGE_REQUEST_CODE = 42; /** * The requestCode for accessing the camera */ private static final int SEND_CAMERA_IMAGE_REQUEST_CODE = 4242; /** * this is true if the popup is currently showing */ private boolean attachPopup = true; /** * Here should the current buddy be stored. That means the buddyId of the * chat that is currently opened. If there is no open chat (e.g. I am in * the {@link ChatListFragment}) this should contain {@link * Constants#BUDDY_ID}. */ private String currentBuddyId = Constants.BUDDY_ID; /** * Here should the current chatName be stored. That means the name of the * chat that is currently opened. If there is no open chat (e.g. I am in * the {@link ChatListFragment}) this should contain {@link * Constants#CHAT_NAME}. */ private String currentChatName = Constants.CHAT_NAME; /** * if the user wants to share an image this imageUri is set in the onCreate. */ private Uri imageUri = null; /** * this is a handler that might be assigned if needed. It may be used for * doing ui related tasks from a background thread */ private Handler mHandler; private EmojiconEditText currentEmojiconEditText = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // The namespace I am using in the UploadService is the app id UploadService.NAMESPACE = BuildConfig.APPLICATION_ID; // the layout contains just the toolbar and the FrameLayout which // contains every fragment setContentView(R.layout.activity_chat); // manage the backstack, therefore, I got a function that checks whether I // can go back and sets the back button in the actionbar accordingly getSupportFragmentManager().addOnBackStackChangedListener(this); shouldDisplayHomeUp(); // in case this is the first time the app launches or in case the user // deleted all application data set my buddyId and pwd I use to log into // the XMPP server setUserPwd(); // if there is a calling intent and this contains a buddyId and chatName // do not open the chatFragmet instantly but only set the current // variables in order to be able to ask for a password if necessary, see // onResume. Intent callingIntent = getIntent(); Bundle extras = callingIntent.getExtras(); String type = callingIntent.getType(); if (Intent.ACTION_SEND.equals(callingIntent.getAction()) && type != null) { if (type.startsWith("image/")) { imageUri = callingIntent.getParcelableExtra(Intent.EXTRA_STREAM); } } if (extras != null && extras.containsKey(Constants.BUDDY_ID) && extras.containsKey(Constants.CHAT_NAME)) { currentBuddyId = extras.getString(Constants.BUDDY_ID); currentChatName = extras.getString(Constants.CHAT_NAME); } } @Override protected void onResume() { super.onResume(); // start the messageService just in case it isn't already started or got // terminated (which should never happen as it boots with the system) startService(new Intent(this, MessageService.class)); //if the user wants a pwd protection and if we want to ask for a password // start the password activity for a result. if (PreferenceManager.getDefaultSharedPreferences(getApplication()) .getBoolean(getResources().getString(R.string.pref_key_enablepwd), false) && getSharedPreferences(Constants.PREFERENCES, 0).getBoolean(Constants.PWD_REQUEST, true)) { startActivityForResult(new Intent(this, PasswordActivity.class), PasswordActivity.ASK_PWD_REQUEST); } else { // otherwise just initialize the activity which means opening the // correct fragment init(); } XmppManager.getInstance(getApplicationContext()).setStatus(true, "0"); // set the last presence I sent to online (0) getSharedPreferences(Constants.PREFERENCES, 0).edit().putLong(Constants.LAST_PRESENCE_SENT, 0).apply(); // set the pwd request variable because we always want to ask for a pwd // if this activity gets resumed if it is not specified otherwise. That // means if something goes wrong we will ask for a pwd too much which is // better than to not ask for a pwd. getSharedPreferences(Constants.PREFERENCES, 0).edit().putBoolean(Constants.PWD_REQUEST, true).apply(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); // this method is actually called before onResume after I rotated the // device, therefore, this is for not asking for a pwd if I rotated. getSharedPreferences(Constants.PREFERENCES, 0).edit().putBoolean(Constants.PWD_REQUEST, false).apply(); } @Override protected void onPause() { // for fun start the MessageService. Just making sure. startService(new Intent(getApplicationContext(), MessageService.class)); // set my status to the current time as I am about to go offline. // Also set the last presence I sent to it. Long time = new Date().getTime(); XmppManager.getInstance().setStatus(true, Long.toString(time)); getSharedPreferences(Constants.PREFERENCES, 0).edit().putLong(Constants.LAST_PRESENCE_SENT, time).apply(); super.onPause(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // "You must return true for the menu to be displayed;[...]" // thanks android :) return false; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); // noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } public void onSettingsClick(MenuItem menuItem) { // clicking the settings entry in the optionsMenu will launch the // settingsActivity and also signal not to ask for a pwd the next time // someone asks for a pwd. // TODO: Yep for now if you close the app while in settings and then // relaunch you won't be asked for a pwd... Maybe going to fix this Intent intent = new Intent(this, SettingsActivity.class); startActivity(intent); getSharedPreferences(Constants.PREFERENCES, 0).edit().putBoolean(Constants.PWD_REQUEST, false).apply(); } public void onAddChatClick(final MenuItem menuItem) { // this button in the optionsMenu will open a dialog containing every // buddy of your roster. That way you can contact each buddy even if you // never had contact with him. String title = getResources().getString(R.string.add_chat_title); // retrieving the roster and saving it in one rosterEntry[] for the full // roster and in one nameList[]. This is seperate in order to ensure // there is a name. If there is no name saved in the roster I will take // the buddyId but without the server and resource part. XmppManager xmppManager = XmppManager.getInstance(); final RosterEntry[] rosterList = xmppManager.listRoster(); final String[] nameList = new String[rosterList.length]; for (int i = 0; i < rosterList.length; i++) { nameList[i] = rosterList[i].getName(); if (nameList[i] == null) { nameList[i] = rosterList[i].getUser(); int index = nameList[i].indexOf('@'); if (index >= 0) nameList[i] = nameList[i].substring(0, index); } } // pretty straight forward setting the title and items and then showing it new AlertDialog.Builder(this).setTitle(title).setItems(nameList, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // clicking on one item will open the chatFragment with the // correct buddyId and name. First I save the buddy in the db // and afterwards I retrieve the buddy of the db, because if // the buddy already exists I want to show the name I // previously saved in the db and not the name of the roster // as these are not synced. MessageHistory messageHistory = new MessageHistory(ChatActivity.this); messageHistory.addChat(rosterList[which].getUser(), nameList[which]); onChatOpened(rosterList[which].getUser(), messageHistory.getName(rosterList[which].getUser())); } }).show(); } public void onUpdateClick(MenuItem menuItem) { // save a handler for the background thread be able to do ui operations mHandler = new Handler(); // start the update process new Thread(new updateRunnable()).start(); } public void onAboutClick(MenuItem menuItem) { // will show the user the versionName with a button to dismiss the dialog try { String versionName = getPackageManager().getPackageInfo(getPackageName(), 0).versionName; new AlertDialog.Builder(this).setTitle(R.string.action_about).setMessage(versionName) .setNegativeButton(R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }).create().show(); } catch (Exception e) { e.printStackTrace(); } } public void sendLibraryImage(View view) { // hide the popup View v = findViewById(R.id.popup_layout); if (v != null) { v.setVisibility(View.GONE); attachPopup = false; } // when clicking attack the user should at first select an application to // choose the image with and then choose an image. // this intent is for getting the image Intent getIntent = new Intent(Intent.ACTION_GET_CONTENT); getIntent.setType("image/*"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) getIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); // and this for getting the application to get the image with Intent pickIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); pickIntent.setType("image/*"); // and this finally is for opening the chooserIntent for opening the // getIntent for returning the image uri. Yep, thanks android Intent chooserIntent = Intent.createChooser(getIntent, getResources().getString(R.string.select_image)); chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { pickIntent }); startActivityForResult(chooserIntent, SEND_LIBRARY_IMAGE_REQUEST_CODE); // nope I don't want to be asked for a pwd when selected the image getSharedPreferences(Constants.PREFERENCES, 0).edit().putBoolean(Constants.PWD_REQUEST, false).apply(); } public void sendCameraImage(View view) { // hide the popup View v = findViewById(R.id.popup_layout); if (v != null) { v.setVisibility(View.GONE); attachPopup = false; } Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); Uri fileUri = Uri.fromFile(MyFileUtils.getFileName()); cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); startActivityForResult(cameraIntent, SEND_CAMERA_IMAGE_REQUEST_CODE); } /** * this runnable will check for an update and if there is an update * available ask the user to download it and then start the download */ private class updateRunnable implements Runnable { @Override public void run() { // this site will post the most current apk of the app final String getCurrentUrl = "http://raspi-server.ddns" + ".net/ChatApp/current.php"; HttpURLConnection connection = null; try { // init the connection to the server URL url = new URL(getCurrentUrl); connection = (HttpURLConnection) url.openConnection(); InputStream is = connection.getInputStream(); InputStreamReader isr = new InputStreamReader(is); int data = isr.read(); String temp = ""; // get the data char by char while (data != -1) { char current = (char) data; temp += current; data = isr.read(); } // extract the versionCode (delete the '.apk' at the end) final String result = temp.substring(0, temp.length() - 4); // check whether the version is more current than the one currently // installed one int version = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode; int targetVersion = Integer.valueOf(result); if (targetVersion > version) { // if that is the case post a dialog to the ui thread to be shown // to ask the user whether he want's to download the app now mHandler.post(new Runnable() { @Override public void run() { new AlertDialog.Builder(ChatActivity.this).setTitle(R.string.update_available) .setMessage(R.string.load_update) .setPositiveButton(R.string.download, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // if he clicks yes start the download downloadUpdate(result); dialog.dismiss(); } }).setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // otherwise just dismiss the dialog dialog.dismiss(); } }).create().show(); } }); } else { mHandler.post(new Runnable() { @Override public void run() { // just post a dialog to the ui to indicate that the app is // up-to-date new AlertDialog.Builder(ChatActivity.this).setTitle(R.string.up_to_date) .setNegativeButton(R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }).create().show(); } }); } } catch (Exception e) { e.printStackTrace(); mHandler.post(new Runnable() { @Override public void run() { // if there was an error signal so. new AlertDialog.Builder(ChatActivity.this).setMessage(R.string.check_update_error) .setNegativeButton(R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }).create().show(); } }); } finally { // make sure to disconnect if (connection != null) connection.disconnect(); } } } /** * will start the asyncTask to download the update * * @param version the version to download */ private void downloadUpdate(String version) { MyFileUtils mfu = new MyFileUtils(); // if we have access to the external storage if (mfu.isExternalStorageWritable()) { // get the default download location and execute the asyncTask UpdateAppAsyncTask asyncTask = new UpdateAppAsyncTask(); File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), version + ".apk"); asyncTask.execute(new String[] { version + ".apk", file.getAbsolutePath() }); } else { new AlertDialog.Builder(ChatActivity.this).setTitle(R.string.download_failed) .setNegativeButton(R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }).show(); } } /** * this AsyncTask will download the most current version of the app in the * background while displaying a ProgressDialog and after finishing * downloading it will install the app. */ private class UpdateAppAsyncTask extends AsyncTask<String[], Integer, Boolean> { private ProgressDialog updateDownloadProgressDialog; private String fileLocation = ""; @Override protected void onPreExecute() { super.onPreExecute(); // initialize the progressDialog updateDownloadProgressDialog = new ProgressDialog(ChatActivity.this); updateDownloadProgressDialog.setMessage(getResources().getString(R.string.downloading_update)); updateDownloadProgressDialog.setCancelable(false); updateDownloadProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); updateDownloadProgressDialog.setProgress(0); updateDownloadProgressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getResources().getString(R.string.cancel), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // the cancel button will cancel the asyncTask and dismiss // the progressDialog UpdateAppAsyncTask.this.cancel(true); updateDownloadProgressDialog.dismiss(); } }); updateDownloadProgressDialog.show(); } @Override protected Boolean doInBackground(String[]... params) { // the params contains the fileName to be downloaded and the location // where the file should be downloaded to. String urlToDownload = "http://raspi-server.ddns.net/ChatApp/binary/" + params[0][0]; String fileLocation = params[0][1]; this.fileLocation = fileLocation; try { // initialize the connection and input/outputStreams URL url = new URL(urlToDownload); URLConnection connection = url.openConnection(); int fileLength = connection.getContentLength(); InputStream input = new BufferedInputStream(connection.getInputStream()); OutputStream output = new FileOutputStream(fileLocation); // start the download and every 20 ms publish a progress byte data[] = new byte[4096]; long total = 0; int count; long start = new Date().getTime(); while (!isCancelled() && (count = input.read(data)) != -1) { total += count; output.write(data, 0, count); if ((new Date().getTime() - start) % 20 == 0) { publishProgress((int) (total * 100 / fileLength)); } } // close everything output.flush(); output.close(); input.close(); return true; } catch (Exception e) { e.printStackTrace(); } return false; } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); Log.d("UPDATE PROGRESS", "Progress: " + values[0]); // just set the progress of the progressDialog updateDownloadProgressDialog.setProgress(values[0]); } @Override protected void onPostExecute(Boolean aBoolean) { super.onPostExecute(aBoolean); // dismiss the dialog and if successful, start the install Activity, // otherwise, show an alert that downloading failed. updateDownloadProgressDialog.dismiss(); if (aBoolean) { Intent installIntent = new Intent(Intent.ACTION_VIEW); installIntent.setDataAndType(Uri.fromFile(new File(fileLocation)), "application/vnd.android.package-archive"); installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(installIntent); } else { new AlertDialog.Builder(ChatActivity.this).setTitle(R.string.download_failed) .setNegativeButton(R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }).show(); } } } public void onDatabaseDebug(MenuItem menuItem) { //this opens the debug option to look and manage the databases. and of // course I don't want a pwd request Intent intent = new Intent(this, AndroidDatabaseManager.class); startActivity(intent); getSharedPreferences(Constants.PREFERENCES, 0).edit().putBoolean(Constants.PWD_REQUEST, false).apply(); } /** * Sets my username and password used to log into the XMPP server if they * aren't already set.<br> * The reason I am not hard coding these or making them constants is for * further improvements to be made easier. Probably I want the user to * choose his buddyId, so I can make a login from within the app possible. */ private void setUserPwd() { //this is straight forward. SharedPreferences preferences = getSharedPreferences(Constants.PREFERENCES, 0); if (!preferences.contains(Constants.USERNAME)) preferences.edit().putString(Constants.USERNAME, "dummy").apply(); if (!preferences.contains(Constants.PASSWORD)) preferences.edit().putString(Constants.PASSWORD, "passwdDummy").apply(); } @Override public void onChatOpened(String buddyId, String name) { // set the buddyId and the chat name as arguments for the ChatFragment, // replace it with the current one and add it to the backstack by its // classname. Also make sure the current variables are set correctly. ChatFragment fragment = ChatFragment.newInstance(buddyId, name, imageUri); getSupportFragmentManager().beginTransaction().replace(R.id.root_view, fragment) .addToBackStack(ChatFragment.class.getName()).commit(); currentBuddyId = buddyId; currentChatName = name; } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); // if the user chose a image to be sent to the current buddy switch (requestCode) { case SEND_CAMERA_IMAGE_REQUEST_CODE: if (resultCode == RESULT_OK && data != null) sendImages(data.getData()); break; case SEND_LIBRARY_IMAGE_REQUEST_CODE: if (resultCode == Activity.RESULT_OK) { if (data.getData() != null) // only one image was selected sendImages(data.getData()); else if (data.getClipData() != null) { // multiple images were selected ArrayList<Uri> uris = new ArrayList<>(); ClipData clipData = data.getClipData(); for (int i = 0; i < clipData.getItemCount(); i++) uris.add(clipData.getItemAt(i).getUri()); Uri[] array = new Uri[uris.size()]; sendImages(uris.toArray(array)); } } break; case PasswordActivity.ASK_PWD_REQUEST: // if the user has entered a password or exited the passwordActivity // otherwise. if (resultCode == Activity.RESULT_OK) { // if the pwd was correct do not request a new pwd. Yep this function // is called before onResume, therefore, this would end in an // infinity loop otherwise... getSharedPreferences(Constants.PREFERENCES, 0).edit().putBoolean(Constants.PWD_REQUEST, false) .apply(); // actually I am not sure, shouldn't the onResume function be called // which calls init? init(); } else { // if (PreferenceManager.getDefaultSharedPreferences(getApplication()) // .getBoolean( // getResources().getString(R.string.pref_key_enablepwd), // true)) // startActivityForResult(new Intent(this, PasswordActivity.class), // PasswordActivity.ASK_PWD_REQUEST); // I suppose I should finish the activity here as the user pressed // back. TODO: I am not sure whether on resume gets called and finish // will really finish if the passwordActivity is called... finish(); } break; } } @Override public void sendImages(Uri... imageUris) { // open the sendImageFragment for the user to add a description and // probably scale the image or whatever I wanna add there. SendImageFragment fragment = SendImageFragment.newInstance(currentBuddyId, currentChatName, imageUris); // replace the fragment and also add it to the backstack by its name. getSupportFragmentManager().beginTransaction().replace(R.id.root_view, fragment) .addToBackStack(SendImageFragment.class.getName()).commit(); } @Override public void onAttachClicked(View view) { View v = findViewById(R.id.popup_layout); if (v != null) { v.setVisibility(attachPopup ? View.GONE : View.VISIBLE); attachPopup = !attachPopup; } } @Override public void onBackStackChanged() { // every time I click back or add an item to the backstack I want to check // whether I want to display the home button in the actionBar or not. shouldDisplayHomeUp(); // make sure that the popup is gone View v = findViewById(R.id.popup_layout); if (v != null) { v.setVisibility(View.GONE); attachPopup = false; } } /** * chooses whether to display the back button in the actionBar and act * correspondingly (enabling/disabling the back button and resetting the * {@link #currentBuddyId} and {@link #currentChatName} if necessary. */ private void shouldDisplayHomeUp() { // If I can back enable those two parameters for the actionBar, they // together enable the back button in the upper left corner. boolean canBack = getSupportFragmentManager().getBackStackEntryCount() > 0; getSupportActionBar().setDisplayHomeAsUpEnabled(canBack); getSupportActionBar().setHomeButtonEnabled(canBack); if (!canBack) { currentBuddyId = Constants.BUDDY_ID; currentChatName = Constants.CHAT_NAME; } } /** * initialize the activity. That means opening the correct fragments, * propagating the backstack and canceling all active notifications. */ private void init() { // if the buddyId equals the constant the chatListFragment should be // opened and if there are no open fragments I want to open the // listFragment and then open the chat in order to have a correct // backstack. Maybe this is possible in a more performant way with the // notfication backstack possibilities but well this works and is easy! if (Constants.BUDDY_ID.equals(currentBuddyId)) getSupportFragmentManager().beginTransaction().replace(R.id.root_view, ChatListFragment.newInstance()) .commit(); else if (getSupportFragmentManager().getFragments() == null) { //for propagating the backstack... getSupportFragmentManager().beginTransaction().replace(R.id.root_view, ChatListFragment.newInstance()) .commit(); onChatOpened(currentBuddyId, currentChatName); } ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).cancel(Notification.NOTIFICATION_ID); new Notification(this).reset(); } @Override public boolean onSupportNavigateUp() { // like what the fuck should the up button from the actionBar do // otherwise? Why do I need to implement this? getSupportFragmentManager().popBackStack(); return true; } @Override public void onReturnClick() { // this function is for the sendImageFragment to pop the backstack when // clicking cancel or send or pressing back. getSupportFragmentManager().popBackStack(); } }