package com.dimtion.shaarlier.services; import android.app.IntentService; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; import android.support.v4.app.TaskStackBuilder; import android.util.Log; import android.widget.Toast; import com.dimtion.shaarlier.R; import com.dimtion.shaarlier.helpers.AccountsSource; import com.dimtion.shaarlier.helpers.NetworkManager; import com.dimtion.shaarlier.helpers.NetworkUtils; import com.dimtion.shaarlier.utils.Link; import com.dimtion.shaarlier.utils.ShaarliAccount; import java.io.IOException; public class NetworkService extends IntentService { public static final String EXTRA_MESSENGER="com.dimtion.shaarlier.networkservice.EXTRA_MESSENGER"; public static final int NO_ERROR = 0; public static final int NETWORK_ERROR = 1; public static final int TOKEN_ERROR = 2; public static final int LOGIN_ERROR = 3; public static final int RETRIEVE_TITLE_ID = 100; public static final int PREFETCH_LINK = 101; public static final int INTENT_CHECK = 201; public static final int INTENT_POST = 202; public static final int INTENT_PREFETCH = 203; public static final int INTENT_RETRIEVE_TITLE_AND_DESCRIPTION = 204; // Notification channels public static final String CHANNEL_ID = "error_channel"; private String loadedTitle; private Context mContext; private Handler mToastHandler; private Exception mError; private ShaarliAccount mShaarliAccount; private String loadedDescription; public NetworkService() { super("NetworkService"); } @Override public void onCreate(){ super.onCreate(); mContext = this; mToastHandler = new Handler(Looper.getMainLooper()); this.createNotificationChannel(); } @Override protected void onHandleIntent(Intent intent) { int action = intent.getIntExtra("action", -1); switch (action) { case INTENT_CHECK: ShaarliAccount accountToTest = (ShaarliAccount) intent.getSerializableExtra("account"); int shaarliLstatus = checkShaarli(accountToTest); Exception object = shaarliLstatus == NETWORK_ERROR ? mError : null; sendBackMessage(intent, shaarliLstatus, object); break; case INTENT_POST: Link link = (Link) intent.getSerializableExtra("link"); if ("".equals(link.getTitle()) && this.loadedTitle != null) { link.setTitle(this.loadedTitle); this.loadedTitle = null; } if ("".equals(link.getDescription()) && this.loadedDescription != null) { link.setDescription(this.loadedDescription); this.loadedDescription = null; } long accountId = intent.getLongExtra("chosenAccountId", -1); try { AccountsSource acs = new AccountsSource(this); mShaarliAccount = (accountId != -1 ? acs.getShaarliAccountById(accountId) : acs.getDefaultAccount()); } catch (Exception e) { e.printStackTrace(); sendNotificationShareError(link); } postLink(link); stopSelf(); break; case INTENT_PREFETCH: Link sharedLink = (Link) intent.getSerializableExtra("link"); mShaarliAccount = sharedLink.getAccount(); Link prefetchedLink = prefetchLink(sharedLink); sendBackMessage(intent, PREFETCH_LINK, prefetchedLink); break; case INTENT_RETRIEVE_TITLE_AND_DESCRIPTION: this.loadedTitle = ""; this.loadedDescription = ""; String url = intent.getStringExtra("url"); boolean autoTitle = intent.getBooleanExtra("autoTitle", true); boolean autoDescription = intent.getBooleanExtra("autoDescription", false); String[] pageTitleAndDescription = getPageTitleAndDescription(url); if (autoTitle){ this.loadedTitle = pageTitleAndDescription[0]; } if (autoDescription){ this.loadedDescription = pageTitleAndDescription[1]; } sendBackMessage(intent, RETRIEVE_TITLE_ID, pageTitleAndDescription); break; default: // Do nothing Log.e("NETWORK_ERROR", "Unknown intent action received: " + action); break; } } private void sendBackMessage(@NonNull Intent intent, int message_id, @Nullable Object message_content) { // Load the messenger to communicate back to the activity Messenger messenger = (Messenger) intent.getExtras().get(EXTRA_MESSENGER); Message msg = Message.obtain(); msg.arg1 = message_id; msg.obj = message_content; try { assert messenger != null; messenger.send(msg); } catch (android.os.RemoteException | AssertionError e1) { Log.w(getClass().getName(), "Exception sending message", e1); } } /** * Display Toast in the main thread * Thanks : http://stackoverflow.com/a/3955826 */ private class DisplayToast implements Runnable{ private final String mText; public DisplayToast(String text){ mText = text; } public void run(){ Toast.makeText(mContext, mText, Toast.LENGTH_SHORT).show(); } } /** * Check if the given credentials are correct * @param account The account with the credentials * @return NO_ERROR if nothing is wrong */ private int checkShaarli(ShaarliAccount account){ NetworkManager manager = NetworkUtils.getNetworkManager(account); try { if (!manager.isCompatibleShaarli()) { return TOKEN_ERROR; } if (!manager.login()) { return LOGIN_ERROR; } } catch (IOException e) { mError = e; return NETWORK_ERROR; } return NO_ERROR; } /** * Try to prefetch the data of the link * Will return exactly the same link if the link does not exist * or if the prefetch failed. * * @param sharedLink partial link * @return new link with the prefetched data */ private Link prefetchLink(Link sharedLink) { Link prefetchedLink = new Link(sharedLink); try { NetworkManager manager = NetworkUtils.getNetworkManager(sharedLink.getAccount()); if (manager.isCompatibleShaarli() && manager.login()) { prefetchedLink = manager.prefetchLinkData(sharedLink); } else { mError = new Exception("Could not connect to the shaarli. Possibles causes : unhandled shaarli, bad username or password"); Log.e("ERROR", mError.getMessage()); } } catch (IOException | NullPointerException e) { mError = e; Log.e("ERROR", mError.getMessage()); } return prefetchedLink; } private void postLink(Link link) { boolean posted = true; // Assume it is shared try { // Connect the user to the site : NetworkManager manager = NetworkUtils.getNetworkManager(mShaarliAccount); if (manager.isCompatibleShaarli() && manager.login()) { manager.pushLink(link); } else { mError = new Exception("Could not connect to the shaarli. Possibles causes : unhandled shaarli, bad username or password"); posted = false; } } catch (IOException | NullPointerException e) { mError = e; Log.e("ERROR", e.getMessage()); posted = false; } if (!posted) { sendNotificationShareError(link); } else { mToastHandler.post(new DisplayToast(getString(R.string.add_success))); Log.i("SUCCESS", "Success while sharing link"); } } /** * Retrieve the title of a page * @param url the page to get the title * @return the title page, "" if there is an error */ @NonNull private String[] getPageTitleAndDescription(String url){ return NetworkUtils.loadTitleAndDescription(url); } private void sendNotificationShareError(Link link) { NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, CHANNEL_ID) .setSmallIcon(R.drawable.ic_launcher) .setContentTitle("Failed to share " + link.getTitle()) .setContentText("Press to try again") .setAutoCancel(true) .setPriority(NotificationCompat.PRIORITY_LOW); // Creates an explicit intent To relaunch this service Intent resultIntent = new Intent(this, NetworkService.class); resultIntent.putExtra("action", NetworkService.INTENT_POST); resultIntent.putExtra("link", link); resultIntent.putExtra(Intent.EXTRA_TEXT, link.getUrl()); resultIntent.putExtra(Intent.EXTRA_SUBJECT, link.getTitle()); // 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(this); // Adds the Intent that starts the Activity to the top of the stack stackBuilder.addNextIntent(resultIntent); PendingIntent resultPendingIntent = PendingIntent.getService(this, 0, resultIntent, 0); mBuilder.setContentIntent(resultPendingIntent); NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); // mId allows you to update the notification later on. mNotificationManager.notify(link.getUrl().hashCode(), mBuilder.build()); } private void createNotificationChannel() { // Create the NotificationChannel, but only on API 26+ because // the NotificationChannel class is new and not in the support library if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { CharSequence name = getString(R.string.notification_channel_error_name); String description = getString(R.string.notification_channel_error_description); int importance = NotificationManager.IMPORTANCE_DEFAULT; NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance); channel.setDescription(description); // Register the channel with the system; you can't change the importance // or other notification behaviors after this NotificationManager notificationManager = getSystemService(NotificationManager.class); notificationManager.createNotificationChannel(channel); } } }