Java tutorial
/* BalticApp, for studying and tracking the condition of the Baltic sea and Gulf of Finland throug user submissions. Copyright (C) 2016 Daniel Zakharin, LuKe This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/> or the beginning of MainActivity.java file. */ package com.luke.lukef.lukeapp.tools; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.location.Location; import android.os.AsyncTask; import android.support.v4.content.res.ResourcesCompat; import android.text.TextUtils; import android.util.Log; import android.widget.ImageView; import com.auth0.android.Auth0; import com.auth0.android.authentication.AuthenticationAPIClient; import com.auth0.android.authentication.AuthenticationException; import com.auth0.android.callback.BaseCallback; import com.auth0.android.result.UserProfile; import com.luke.lukef.lukeapp.MainActivity; import com.luke.lukef.lukeapp.NewUserActivity; import com.luke.lukef.lukeapp.R; import com.luke.lukef.lukeapp.WelcomeActivity; import com.luke.lukef.lukeapp.interfaces.Auth0Responder; import com.luke.lukef.lukeapp.model.Category; import com.luke.lukef.lukeapp.model.Link; import com.luke.lukef.lukeapp.model.Rank; import com.luke.lukef.lukeapp.model.SessionSingleton; import com.luke.lukef.lukeapp.model.Submission; import com.luke.lukef.lukeapp.model.UserFromServer; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.ProtocolException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * Contains methods for interaction with the Luke backend. Needs to be intsantiated, unlike LukeUtils, since * context is required for most network operations. */ public class LukeNetUtils { private Context context; private final String TAG = "LukeNetUtils"; public LukeNetUtils(Context context) { this.context = context; } /** * Checks the server if a username is available * * @param usernameToCheck The username that should be checked * @return <b>true</b> if the username exists already, <b>false</b> if not * @throws ExecutionException * @throws InterruptedException */ public boolean checkUsernameAvailable(final String usernameToCheck) throws ExecutionException, InterruptedException { Callable<Boolean> booleanCallable = new Callable<Boolean>() { @Override public Boolean call() throws Exception { try { String response = getMethod( "http://www.balticapp.fi/lukeA/user/available?username=" + usernameToCheck); JSONObject jsonObject; try { jsonObject = new JSONObject(response); // TODO: 17/11/2016 make alert that username is taken if (jsonObject.has("exists")) { return !jsonObject.getBoolean("exists"); } else { return false; } } catch (JSONException e) { Log.e(TAG, "onPostExecute: ", e); return false; } } catch (MalformedURLException e) { Log.e(TAG, "doInBackground: ", e); return false; } catch (IOException e) { Log.e(TAG, "doInBackground: ", e); return false; } } }; FutureTask<Boolean> booleanFutureTask = new FutureTask<>(booleanCallable); Thread thread = new Thread(booleanFutureTask); thread.start(); return booleanFutureTask.get(); } /** * Sets a username for a user in the server * * @param username The username to be set * @return <b>true</b> if the request passes, <b>false</b> if it doesn't * @throws IOException * @throws ExecutionException * @throws InterruptedException */ public boolean setUsername(final String username) throws IOException, ExecutionException, InterruptedException { Callable<Boolean> booleanCallable = new Callable<Boolean>() { @Override public Boolean call() throws Exception { HttpURLConnection httpURLConnection; URL setUsernameUrl = new URL( "http://www.balticapp.fi/lukeA/user/set-username?username=" + username); httpURLConnection = (HttpURLConnection) setUsernameUrl.openConnection(); httpURLConnection.setRequestProperty(context.getString(R.string.authorization), context.getString(R.string.bearer) + SessionSingleton.getInstance().getIdToken()); httpURLConnection.setRequestProperty(context.getString(R.string.acstoken), SessionSingleton.getInstance().getAccessToken()); return httpURLConnection.getResponseCode() == 200; } }; FutureTask<Boolean> booleanFutureTask = new FutureTask<>(booleanCallable); Thread t = new Thread(booleanFutureTask); t.start(); return booleanFutureTask.get(); } /** * Sets a profile image for a user in the server * * @param bitmap The bitmap that should be set * @return <b>true</b> if the update passes, <b>false</b> if not */ public boolean updateUserImage(final Bitmap bitmap) { Callable<Boolean> booleanCallable = new Callable<Boolean>() { @Override public Boolean call() throws Exception { HttpURLConnection conn; //create a json object from this submission to be sent to the server and convert it to string JSONObject jsonObject = new JSONObject(); jsonObject.put("image", LukeUtils.bitmapToBase64String(bitmap)); String urlParameters = jsonObject.toString(); return postMethod("http://www.balticapp.fi/lukeA/user/update", urlParameters); } }; FutureTask<Boolean> futureTask = new FutureTask<>(booleanCallable); Thread t = new Thread(futureTask); t.start(); try { return futureTask.get(); } catch (InterruptedException | ExecutionException e) { Log.e(TAG, "submitToServer: ", e); return false; } } /** * Sends a request to Auth0 to receive a user profile image url * * @param auth0Responder the receiver of the bitmap that auth0 provides */ public void getUserImageFromAuth0(final Auth0Responder auth0Responder) { AuthenticationAPIClient client = new AuthenticationAPIClient( new Auth0(SessionSingleton.getInstance().getAuth0ClientID(), SessionSingleton.getInstance().getAuth0Domain())); client.tokenInfo(SessionSingleton.getInstance().getIdToken()) .start(new BaseCallback<UserProfile, AuthenticationException>() { @Override public void onSuccess(UserProfile payload) { try { auth0Responder.receiveBitmapFromAuth0(getBitmapFromURL(payload.getPictureURL())); } catch (ExecutionException | InterruptedException e) { Log.e(TAG, "onSuccess: Error fetching Auth0 image", e); } } @Override public void onFailure(AuthenticationException error) { } }); } /** * Creates a Bitmap from the given URL * * @param imageUrl The URL of the image * @return The image as an Bitmap object, otherwise <b>null</b> * @throws ExecutionException * @throws InterruptedException */ public Bitmap getBitmapFromURL(final String imageUrl) throws ExecutionException, InterruptedException { Callable<Bitmap> bitmapCallable = new Callable<Bitmap>() { @Override public Bitmap call() throws Exception { try { URL url = new URL(imageUrl); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setDoInput(true); connection.connect(); InputStream input = connection.getInputStream(); return BitmapFactory.decodeStream(input); } catch (IOException e) { Log.e(TAG, "call: ", e); return null; } } }; FutureTask<Bitmap> bitmapFutureTask = new FutureTask<>(bitmapCallable); Thread t = new Thread(bitmapFutureTask); t.start(); return bitmapFutureTask.get(); } /** * Fetches user data from the backend based on given userId * * @param userId The ID of the user whose data is fetched * @return {@link UserFromServer} object containing the user data * @throws ExecutionException * @throws InterruptedException */ public UserFromServer getUserFromUserId(final String userId) throws ExecutionException, InterruptedException { Callable<UserFromServer> userFromServerCallable = new Callable<UserFromServer>() { @Override public UserFromServer call() throws Exception { String jsonString; jsonString = getMethod("http://www.balticapp.fi/lukeA/user?id=" + userId); if (!TextUtils.isEmpty(jsonString)) { final JSONObject jsonObject = new JSONObject(jsonString); return LukeUtils.parseUserFromJsonObject(jsonObject); } else { return null; } } }; FutureTask<UserFromServer> userFromServerFutureTask = new FutureTask<>(userFromServerCallable); Thread t = new Thread(userFromServerFutureTask); t.start(); return userFromServerFutureTask.get(); } /** * Report an inappropriate submissions by id * * @param submissionId The ID of the given submission. * @return <b>true</b> if the request passes, <b>false</b> if it doesn't. */ public String reportSubmission(final String submissionId) { Callable<String> booleanCallable = new Callable<String>() { @Override public String call() throws Exception { String jsonString = getMethod("http://www.balticapp.fi/lukeA/report/flag?id=" + submissionId); Log.e(TAG, "updateUserImage run: Result : " + jsonString); return "Error reporting"; } }; FutureTask<String> booleanFutureTask = new FutureTask<>(booleanCallable); Thread t = new Thread(booleanFutureTask); t.start(); try { return booleanFutureTask.get(); } catch (InterruptedException | ExecutionException e) { Log.e(TAG, "reportSubmission: ", e); return null; } } /** * Fetches all submissions by a user from the server. * * @param userID The ID of the user whose submissions should be fetched. * @return Returns an ArrayList of {@link Submission} objects. */ public ArrayList<Submission> getSubmissionsByUser(final String userID) { Callable<ArrayList<Submission>> booleanCallable = new Callable<ArrayList<Submission>>() { @Override public ArrayList<Submission> call() throws Exception { String jsonString = getMethod("http://www.balticapp.fi/lukeA/report?submitterId=" + userID); if (!TextUtils.isEmpty(jsonString)) { JSONArray jsonArray = new JSONArray(jsonString); Log.e(TAG, "call: jsonArray" + jsonArray.toString()); return LukeUtils.parseSubmissionsFromJsonArray(jsonArray); } else { return null; } } }; FutureTask<ArrayList<Submission>> booleanFutureTask = new FutureTask<>(booleanCallable); Thread t = new Thread(booleanFutureTask); t.start(); try { return booleanFutureTask.get(); } catch (InterruptedException e) { Log.e(TAG, "reportSubmission: ", e); return null; } catch (ExecutionException e) { Log.e(TAG, "reportSubmission: ", e); return null; } } /** * Fetches a thumbnail of the map from Google's API based on given location. * * @param center Center of the wanted thumbnail. * @param width Width in px of the wanted thumbnail. * @param height Height in px of the wanted thumbnail. * @return The thumbnail of the map as a Bitmap. * @throws ExecutionException * @throws InterruptedException */ public Bitmap getMapThumbnail(final Location center, final int width, final int height) throws ExecutionException, InterruptedException { final String urlString1 = "https://maps.googleapis.com/maps/api/staticmap?center=" + center.getLatitude() + ",%20" + center.getLongitude() + "&zoom=16&size=" + width + "x" + height + "&maptype=normal&markers=color:red|" + center.getLatitude() + "," + center.getLongitude(); Log.e(TAG, "getMapThumbnail: Marker url: \n" + urlString1); return getBitmapFromURL(urlString1); } /** * Fetches a single Submission by ID from the server * * @param id The ID of the submission which data needs to be fetched * @return Submission object */ public Submission getSubmissionFromId(String id) { try { String jsonString = getMethod(this.context.getString(R.string.report_id_url) + id); JSONArray jsonArray = new JSONArray(jsonString); Submission submission = LukeUtils.parseSubmissionFromJsonObject(jsonArray.getJSONObject(0)); return submission; } catch (IOException e) { Log.e(TAG, "Exception with fetching data: ", e); return null; } catch (JSONException e) { Log.e(TAG, "getSubmissionFromId: ", e); return null; } } /** * Fetches all existing categories from the server, parses them and adds new ones to the * {@link SessionSingleton}. Duplicates do not get added */ public ArrayList<Category> getCategories() throws ExecutionException, InterruptedException { Callable<ArrayList<Category>> categoriesCallable = new Callable<ArrayList<Category>>() { @Override public ArrayList<Category> call() throws Exception { String jsonString = getMethod("http://www.balticapp.fi/lukeA/category"); JSONArray jsonArr = new JSONArray(jsonString); return LukeUtils.getCategoryObjectsFromJsonArray(jsonArr); } }; FutureTask<ArrayList<Category>> arrayListFutureTask = new FutureTask<ArrayList<Category>>( categoriesCallable); Thread t = new Thread(arrayListFutureTask); t.start(); return arrayListFutureTask.get(); } private void startFetchUserDataTask(WelcomeActivity welcomeActivity) { FetchUserDataTask fetchUserDataTask = new FetchUserDataTask(welcomeActivity); fetchUserDataTask.execute(); } /** * Fetches the currently logged user from the server * @return User object corresponding to the currently logged user * @throws IOException * @throws ExecutionException * @throws InterruptedException */ public UserFromServer getOwnUser() throws IOException, ExecutionException, InterruptedException { Callable<UserFromServer> userFromServerCallable = new Callable<UserFromServer>() { @Override public UserFromServer call() throws Exception { String jsonString = getMethod("http://www.balticapp.fi/lukeA/user/me"); JSONObject jsonObject = new JSONObject(jsonString); return LukeUtils.parseUserFromJsonObject(jsonObject); } }; FutureTask<UserFromServer> userFromServerFutureTask = new FutureTask<UserFromServer>( userFromServerCallable); Thread t = new Thread(userFromServerFutureTask); t.start(); return userFromServerFutureTask.get(); } /** * Fetches user data like ID, image and URL from the server. Sets them into the SessionSigleton */ // TODO: 24.1.2017 use lukeutils getOwnUser method insted private class FetchUserDataTask extends AsyncTask<Void, Void, Void> { private WelcomeActivity welcomeActivity; UserFromServer userFromServer; FetchUserDataTask(WelcomeActivity welcomeActivity) { this.welcomeActivity = welcomeActivity; } @Override protected Void doInBackground(Void... params) { try { userFromServer = getOwnUser(); } catch (IOException | ExecutionException | InterruptedException e) { Log.e(TAG, "doInBackground: ", e); } return null; } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); SessionSingleton.getInstance().setUserId(userFromServer.getId()); SessionSingleton.getInstance().setUserLogged(true); SessionSingleton.getInstance().setScore(userFromServer.getScore()); String usrnm = userFromServer.getUsername(); try { SessionSingleton.getInstance().setUserImage(getBitmapFromURL(userFromServer.getImageUrl())); } catch (ExecutionException | InterruptedException e1) { Log.e(TAG, "onPostExecute: ", e1); } if (!TextUtils.isEmpty(usrnm)) { SessionSingleton.getInstance().setUsername(userFromServer.getUsername()); this.welcomeActivity.startActivity(new Intent(this.welcomeActivity, MainActivity.class)); } else { this.welcomeActivity.startActivity(new Intent(this.welcomeActivity, NewUserActivity.class)); } } } /** * Attempts to log in into the Luke server. If succesful, starts a task to fetch users data * @param welcomeActivity * @param idToken */ public void attemptLogin(final WelcomeActivity welcomeActivity, final String idToken) { Callable<Boolean> booleanCallable = new Callable<Boolean>() { @Override public Boolean call() throws Exception { try { URL lukeURL = new URL(welcomeActivity.getString(R.string.loginUrl)); HttpURLConnection httpURLConnection = (HttpURLConnection) lukeURL.openConnection(); httpURLConnection.setRequestProperty(welcomeActivity.getString(R.string.authorization), welcomeActivity.getString(R.string.bearer) + idToken); if (httpURLConnection.getResponseCode() == 200) { return true; } else { Log.e(TAG, "call: LOGIN DIDN'T WORK"); // TODO: 12/12/2016 DANIEL return false; } } catch (MalformedURLException e) { Log.e(TAG, "call: ", e); return false; } catch (IOException e) { Log.e(TAG, "call: ", e); return false; } } }; FutureTask<Boolean> booleanFutureTask = new FutureTask<>(booleanCallable); Thread thread = new Thread(booleanFutureTask); thread.start(); try { if (booleanFutureTask.get()) { startFetchUserDataTask(welcomeActivity); } else { Log.e(TAG, "onAuthentication: booleanFutureTask failed"); } } catch (InterruptedException | ExecutionException e) { Log.e(TAG, "onAuthentication: ", e); } } /** * Fetches the newest link from the backend. This link will be shown at the start of the app. * Only the newest link will be shown * @return Link object of the newest link on the backend */ public Link getNewestLink() { Callable<Link> linkCallable = new Callable<Link>() { @Override public Link call() throws Exception { String jsonString = getMethod("http://www.balticapp.fi/lukeA/link"); JSONArray jsonArray = new JSONArray(jsonString); Log.e(TAG, "getlinks call: jsonArray" + jsonArray.toString()); List<Link> links = LukeUtils.parseLinksFromJsonArray(jsonArray); return links.get(links.size() - 1); } }; FutureTask<Link> linkFutureTask = new FutureTask<>(linkCallable); Thread t = new Thread(linkFutureTask); t.start(); try { return linkFutureTask.get(); } catch (InterruptedException | ExecutionException e) { Log.e(TAG, "reportSubmission: ", e); return null; } } /** * Fewtches all users from the backend. * @return Arraylist of User objects * @throws ExecutionException * @throws InterruptedException */ public ArrayList<UserFromServer> getAllUsers() throws ExecutionException, InterruptedException { Callable<ArrayList<UserFromServer>> userFromServerCallable = new Callable<ArrayList<UserFromServer>>() { @Override public ArrayList<UserFromServer> call() throws Exception { String jsonString = getMethod("http://www.balticapp.fi/lukeA/user/get-all"); Log.e(TAG, "doInBackground: STRING IS " + jsonString); if (!TextUtils.isEmpty(jsonString)) { ArrayList<UserFromServer> returnjeeben = new ArrayList<>(); JSONArray jsonArray = new JSONArray(jsonString); for (int i = 0; i < jsonArray.length(); i++) { returnjeeben.add(LukeUtils.parseUserFromJsonObject(jsonArray.getJSONObject(i))); } return returnjeeben; } else { return null; } } }; FutureTask<ArrayList<UserFromServer>> userFromServerFutureTask = new FutureTask<>(userFromServerCallable); Thread t = new Thread(userFromServerFutureTask); t.start(); return userFromServerFutureTask.get(); } /** * Fetches all ranks from the backend * @return Arraylist of Rank objects * @throws ExecutionException * @throws InterruptedException */ public ArrayList<Rank> getAllRanks() throws ExecutionException, InterruptedException { Callable<ArrayList<Rank>> arrayListCallable = new Callable<ArrayList<Rank>>() { @Override public ArrayList<Rank> call() throws Exception { String allRanks = getMethod("http://www.balticapp.fi/lukeA/rank"); JSONArray allRanksJson = new JSONArray(allRanks); ArrayList<Rank> ranks = LukeUtils.parseRanksFromJsonArray(allRanksJson); return ranks; } }; FutureTask<ArrayList<Rank>> arrayListFutureTask = new FutureTask<ArrayList<Rank>>(arrayListCallable); Thread t = new Thread(arrayListFutureTask); t.start(); return arrayListFutureTask.get(); } /** * Generic get method for the luke api * @param urlString Url to send the request to * @return Response of the request as a String * @throws IOException */ private String getMethod(String urlString) throws IOException { URL lukeURL = new URL(urlString); HttpURLConnection httpURLConnection = (HttpURLConnection) lukeURL.openConnection(); httpURLConnection.setRequestProperty(context.getString(R.string.authorization), context.getString(R.string.bearer) + SessionSingleton.getInstance().getIdToken()); if (httpURLConnection.getResponseCode() == 200) { BufferedReader bufferedReader = new BufferedReader( new InputStreamReader(httpURLConnection.getInputStream())); String jsonString = ""; StringBuilder stringBuilder = new StringBuilder(); String line; while ((line = bufferedReader.readLine()) != null) { stringBuilder.append(line).append("\n"); } jsonString = stringBuilder.toString(); bufferedReader.close(); return jsonString; } else { //TODO: if error do something else, ERROR STREAM Log.e(TAG, "doInBackground: ERROR " + httpURLConnection.getResponseCode()); return null; } } /** * Generic post method for the luke api. * @param urlString Url to send the request to * @param params Parameters to send with the request as a String * @return boolean indicating the success of the request */ private boolean postMethod(String urlString, String params) { try { HttpURLConnection conn; URL url = new URL(urlString); conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty(context.getString(R.string.authorization), context.getString(R.string.bearer) + SessionSingleton.getInstance().getIdToken()); conn.setRequestProperty("Content-Type", "application/json"); conn.setRequestProperty("charset", "utf-8"); conn.setDoOutput(true); //get the output stream of the connection OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream()); //write the JSONobject to the connections output writer.write(params); //flush and close the writer writer.flush(); writer.close(); //get the response, if successfull, get inputstream, if unsuccessful get errorStream BufferedReader bufferedReader; Log.e(TAG, "updateUserImage call: RESPONSE CODE:" + conn.getResponseCode()); if (conn.getResponseCode() != 200) { bufferedReader = new BufferedReader(new InputStreamReader(conn.getErrorStream())); } else { // TODO: 25/11/2016 check for authorization error, respond accordingly bufferedReader = new BufferedReader(new InputStreamReader(conn.getInputStream())); } String jsonString; StringBuilder stringBuilder = new StringBuilder(); String line2; while ((line2 = bufferedReader.readLine()) != null) { stringBuilder.append(line2).append("\n"); } bufferedReader.close(); jsonString = stringBuilder.toString(); Log.e(TAG, "updateUserImage run: Result : " + jsonString); conn.disconnect(); return true; } catch (IOException e) { Log.e(TAG, "postMethod: ", e); return false; } } /** * Method for asynchronously fetching an image from the internet and setting it into an imageView. * Allows bigger image download to not pause the app. * @param imageViewToSet ImageView where the image will be set * @param url Url of the image as a String * @param defaultImageId Default image id, in case the online image is unavailable * @param activity Activity that can run setting the imageView on a UiThread */ public static void imageSetupTask(ImageView imageViewToSet, String url, final int defaultImageId, Activity activity) { class LoadImageTask extends AsyncTask<Void, Void, Void> { private ImageView imageView; private String urlString; private Activity activity; private Bitmap bitmap = null; int defaultId; LoadImageTask(ImageView imageView, String urlString, Activity activity, int defaultId) { this.activity = activity; this.urlString = urlString; this.imageView = imageView; this.defaultId = defaultId; } @Override protected Void doInBackground(Void... params) { if (this.urlString != null) { LukeNetUtils lukeNetUtils = new LukeNetUtils(this.activity); try { this.bitmap = lukeNetUtils.getBitmapFromURL(urlString); } catch (ExecutionException | InterruptedException e) { Log.e("ImageAsyncTask", "doInBackground: ", e); } } else { this.bitmap = null; } return null; } @Override protected void onPostExecute(Void aVoid) { this.activity.runOnUiThread(new Runnable() { @Override public void run() { if (LoadImageTask.this.bitmap == null) { LoadImageTask.this.imageView.setImageDrawable( ResourcesCompat.getDrawable(activity.getResources(), defaultImageId, null)); } else { LoadImageTask.this.imageView.setImageBitmap(LoadImageTask.this.bitmap); } } }); super.onPostExecute(aVoid); } } LoadImageTask bitmapTask = new LoadImageTask(imageViewToSet, url, activity, defaultImageId); bitmapTask.execute(); } }