package com.catchingnow.tinyclipboardmanager; import android.annotation.TargetApi; import android.app.Notification; import android.app.PendingIntent; import android.app.Service; import android.app.job.JobInfo; import android.app.job.JobScheduler; import android.content.ClipData; import android.content.ClipDescription; import android.content.ClipboardManager; import android.content.ClipboardManager.OnPrimaryClipChangedListener; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.preference.PreferenceManager; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationManagerCompat; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; import android.view.View; import android.widget.RemoteViews; import android.widget.Toast; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; public class CBWatcherService extends Service { public final static String INTENT_EXTRA_FORCE_SHOW_NOTIFICATION = "com.catchingnow.tinyclipboardmanager.EXTRA.FORCE_SHOW_NOTIFICATION"; public final static String INTENT_EXTRA_MY_ACTIVITY_ON_FOREGROUND_MESSAGE = "com.catchingnow.tinyclipboardmanager.EXTRA.MY_ACTIVITY_ON_FOREGROUND_MESSAGE"; public final static String INTENT_EXTRA_CHANGE_STAR_STATUES = "com.catchingnow.tinyclipboardmanager.EXTRA.CHANGE_STAR_STATUES"; public final static String INTENT_EXTRA_TEMPORARY_STOP = "com.catchingnow.tinyclipboardmanager.EXTRA.TEMPORARY_STOP"; public final static String INTENT_EXTRA_CHECK_CLIPBOARD_NOW = "com.catchingnow.tinyclipboardmanager.EXTRA.CHECK_CLIPBOARD_NOW"; public final static String ON_DESTROY = "onCBWatcherServiceDestroy"; public final static int JOB_ID = 1; public int NUMBER_OF_CLIPS = 5; //3-6 private final static String NOTIFICATION_GROUP = "notification_group"; private Context mContext; private NotificationManagerCompat notificationManager; private ClipboardManager clipboardManager; private SharedPreferences preference; private Storage db; private Handler mHandler; boolean allowService = true; boolean allowNotification = true; protected boolean isStarred = false; protected boolean temporaryStop = false; private boolean pinOnTop = false; private int notificationPriority = 0; private int isMyActivitiesOnForeground = 0; private int pIntentId = 999; private OnPrimaryClipChangedListener listener = new OnPrimaryClipChangedListener() { public void onPrimaryClipChanged() { performClipboardCheck(); } }; @Override public void onCreate() { Log.v(MyUtil.PACKAGE_NAME, "onCreate CBService"); mContext = this; mHandler = new Handler(); preference = PreferenceManager.getDefaultSharedPreferences(this); readPreference(); notificationManager = NotificationManagerCompat.from(this); db = Storage.getInstance(this.getBaseContext()); clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); clipboardManager.addPrimaryClipChangedListener(listener); if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { Log.w(MyUtil.PACKAGE_NAME, "Not support JobScheduler"); } else { bindJobScheduler(); } super.onCreate(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { boolean beforeTemporaryStop = temporaryStop; if (intent == null) { intent = new Intent(); } int myActivitiesOnForegroundMessage = intent.getIntExtra(INTENT_EXTRA_MY_ACTIVITY_ON_FOREGROUND_MESSAGE, 0); isMyActivitiesOnForeground += myActivitiesOnForegroundMessage; readPreference(); if (intent.getBooleanExtra(INTENT_EXTRA_CHECK_CLIPBOARD_NOW, false)) { performClipboardCheck(); } if (!allowService) { if (isMyActivitiesOnForeground <= 0) { stopSelf(); isMyActivitiesOnForeground = 0; return Service.START_NOT_STICKY; } } temporaryStop = intent.getBooleanExtra(INTENT_EXTRA_TEMPORARY_STOP, false); if (temporaryStop != beforeTemporaryStop) { showNotification(); Intent it = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); sendBroadcast(it); final String toastText = temporaryStop ? getString(R.string.toast_service_temporary_stop) : getString(R.string.toast_service_temporary_resume); mHandler.post(new Runnable() { @Override public void run() { Toast.makeText( mContext, toastText, Toast.LENGTH_SHORT ).show(); } }); } if (intent.getBooleanExtra(INTENT_EXTRA_CHANGE_STAR_STATUES, false)) { isStarred = !isStarred; showNotification(); return START_STICKY; } if (intent.getBooleanExtra(INTENT_EXTRA_FORCE_SHOW_NOTIFICATION, false)) { showNotification(); return START_STICKY; } return START_STICKY; } @Override public IBinder onBind(Intent intent) { return null; } @Override public void onDestroy() { notificationManager.cancelAll(); ((ClipboardManager) getSystemService(CLIPBOARD_SERVICE)).removePrimaryClipChangedListener(listener); LocalBroadcastManager.getInstance(mContext).sendBroadcast(new Intent(ON_DESTROY)); super.onDestroy(); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void bindJobScheduler() { // JobScheduler for auto clean sqlite JobInfo job = new JobInfo.Builder(JOB_ID, new ComponentName(this, SyncJobService.class)) .setRequiresCharging(true) .setRequiresDeviceIdle(true) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setPeriodic(24*60*60*1000) .setPersisted(true) .build(); JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); jobScheduler.cancel(JOB_ID); jobScheduler.schedule(job); } private void readPreference() { allowService = preference.getBoolean(ActivitySetting.PREF_START_SERVICE, true); allowNotification = preference.getBoolean(ActivitySetting.PREF_NOTIFICATION_SHOW, true); notificationPriority = Integer.parseInt(preference.getString(ActivitySetting.PREF_NOTIFICATION_PRIORITY, "0")); pinOnTop = preference.getBoolean(ActivitySetting.PREF_NOTIFICATION_PIN, false); } private void performClipboardCheck() { Log.v(MyUtil.PACKAGE_NAME, "performClipboardCheck"); if (temporaryStop) return; if (!clipboardManager.hasPrimaryClip()) return; String clipString; try { //Don't use CharSequence .toString()! CharSequence charSequence = clipboardManager.getPrimaryClip().getItemAt(0).getText(); clipString = String.valueOf(charSequence); } catch (Error ignored) { return; } if (clipString.trim().isEmpty()) return; if (clipString.equals("null")) return; if (db.getClipHistory().size() > 0) { if (clipString.equals(db.getClipHistory().get(0).getText())) return; } int isImportant = db.isClipObjectStarred(clipString) ? 1 : 0; db.modifyClip(null, clipString, isImportant); } private boolean checkNotificationPermission() { if (allowNotification && allowService) { return true; } notificationManager.cancelAll(); return false; } private void showNotification() { int notificationID = 1; if (!checkNotificationPermission()) { return; } List<ClipObject> thisClips; if (isStarred) { thisClips = db.getStarredClipHistory(NUMBER_OF_CLIPS - 1); if (db.getClipHistory().size() == 0) { showSingleNotification(); return; } ClipObject topClip = db.getClipHistory().get(0); if (thisClips.size() == 0) { thisClips.add(0, topClip); } else if (!topClip.getText().equals(thisClips.get(0).getText())) { thisClips.add(0, topClip); } } else { thisClips = db.getClipHistory(NUMBER_OF_CLIPS); } int length = thisClips.size(); if (length <= 1 && !isStarred) { showSingleNotification(); return; } thisClips.add(new ClipObject( getString(R.string.clip_notification_single_text), new Date(), isStarred )); length += 1; length = (length > (NUMBER_OF_CLIPS + 1)) ? (NUMBER_OF_CLIPS + 1) : length; Intent openMainDialogIntent = new Intent(this, ClipObjectActionBridge.class) .putExtra(ClipObjectActionBridge.ACTION_CODE, ClipObjectActionBridge.ACTION_OPEN_MAIN_DIALOG); PendingIntent pOpenMainDialogIntent = PendingIntent.getService( this, pIntentId--, openMainDialogIntent, PendingIntent.FLAG_UPDATE_CURRENT ); NotificationCompat.Builder preBuildNotification = new NotificationCompat.Builder(this) .setContentTitle(getString(R.string.clip_notification_title, MyUtil.stringLengthCut(thisClips.get(0).getText()))) //title .setSmallIcon(R.drawable.ic_stat_icon_colorful) .setVisibility(NotificationCompat.VISIBILITY_SECRET) .setColor(getResources().getColor(R.color.primary_light)) .setContentIntent(pOpenMainDialogIntent) .setOngoing(pinOnTop) .setAutoCancel(false) .setGroup(NOTIFICATION_GROUP) .setGroupSummary(true); if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { // 5.1 icon tmp fix preBuildNotification .setSmallIcon(R.drawable.ic_stat_icon) .setColor(getResources().getColor(R.color.accent)); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { String description = getString(R.string.clip_notification_text); if (isStarred) { description = "★ " + description; } preBuildNotification .setContentText(description); } else { preBuildNotification .setContentText(getString(R.string.clip_notification_text_old)); } switch (notificationPriority) { case 0: preBuildNotification.setPriority(NotificationCompat.PRIORITY_MIN); break; case 1: preBuildNotification.setPriority(NotificationCompat.PRIORITY_DEFAULT); break; case 2: preBuildNotification.setPriority(NotificationCompat.PRIORITY_HIGH); break; } NotificationClipListAdapter notificationClipListAdapter = new NotificationClipListAdapter(this.getBaseContext(), thisClips.get(0)); for (int i = 1; i < length; i++) { notificationClipListAdapter.addClips(thisClips.get(i)); } Notification n = preBuildNotification.build(); if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { n.bigContentView = notificationClipListAdapter.build(); } n.icon = R.drawable.ic_stat_icon; notificationManager.notify(0, n); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { for (Notification notification : notificationClipListAdapter.getWearNotifications()) { notificationManager.notify(notificationID++, notification); } } } private void showSingleNotification() { String currentClip = "Clipboard is empty."; ClipboardManager cb = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); if (cb.hasPrimaryClip()) { ClipData cd = cb.getPrimaryClip(); if (cd.getDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) { CharSequence thisClip = cd.getItemAt(0).getText(); if (thisClip != null) { if (!thisClip.toString().isEmpty()) { currentClip = MyUtil.stringLengthCut(thisClip.toString()); } } } } Intent openMainDialogIntent = new Intent(this, ClipObjectActionBridge.class) .putExtra(ClipObjectActionBridge.ACTION_CODE, ClipObjectActionBridge.ACTION_OPEN_MAIN_DIALOG); PendingIntent pOpenMainDialogIntent = PendingIntent.getService( this, pIntentId--, openMainDialogIntent, PendingIntent.FLAG_UPDATE_CURRENT ); Intent openEditorIntent = new Intent(this, ClipObjectActionBridge.class) .putExtra(ClipObjectActionBridge.ACTION_CODE, ClipObjectActionBridge.ACTION_EDIT); PendingIntent pOpenEditorIntent = PendingIntent.getService( this, pIntentId--, openEditorIntent, PendingIntent.FLAG_UPDATE_CURRENT ); NotificationCompat.Builder preBuildN = new NotificationCompat.Builder(this) .setContentIntent(pOpenMainDialogIntent) .setContentTitle(getString(R.string.clip_notification_title, currentClip)) .setOngoing(pinOnTop) .setAutoCancel(false) .setSmallIcon(R.drawable.ic_stat_icon_colorful) .setVisibility(NotificationCompat.VISIBILITY_SECRET) .setColor(getResources().getColor(R.color.primary_light)) .addAction(R.drawable.ic_action_add, getString(R.string.action_add), pOpenEditorIntent); if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { // 5.1 icon tmp fix preBuildN.setSmallIcon(R.drawable.ic_stat_icon); } if (isStarred) { preBuildN .setContentText(getString(R.string.clip_notification_starred_single_text)); } else { preBuildN .setContentText(getString(R.string.clip_notification_single_text)); } switch (notificationPriority) { case 0: preBuildN.setPriority(NotificationCompat.PRIORITY_MIN); break; case 1: preBuildN.setPriority(NotificationCompat.PRIORITY_DEFAULT); break; case 2: preBuildN.setPriority(NotificationCompat.PRIORITY_HIGH); break; } Notification n = preBuildN.build(); n.icon = R.drawable.ic_stat_icon; notificationManager.notify(0, n); } public static void startCBService(Context context, boolean refreshNotification) { startCBService(context, refreshNotification, true, 0); } public static void startCBService(Context context, int myActivitiesOnForegroundMessage) { startCBService(context, false, true, myActivitiesOnForegroundMessage); } public static void startCBService(Context context, boolean refreshNotification, boolean checkClipboardNow) { startCBService(context, refreshNotification, checkClipboardNow, 0); } public static void startCBService(Context context, boolean forceShowNotification, boolean checkClipboardNow, int myActivitiesOnForegroundMessage) { Intent intent = new Intent(context, CBWatcherService.class) .putExtra(INTENT_EXTRA_FORCE_SHOW_NOTIFICATION, forceShowNotification) .putExtra(INTENT_EXTRA_CHECK_CLIPBOARD_NOW, checkClipboardNow) .putExtra(INTENT_EXTRA_MY_ACTIVITY_ON_FOREGROUND_MESSAGE, myActivitiesOnForegroundMessage); context.startService(intent); } private class NotificationClipListAdapter { private int buttonNumber = 9999; private RemoteViews expandedView; private List<ClipObject> clips; private Context context; public NotificationClipListAdapter(Context context, ClipObject clipObject) { this.context = context; String currentClip = clipObject.getText(); clips = new ArrayList<>(); clips.add(clipObject); expandedView = new RemoteViews(this.context.getPackageName(), R.layout.notification_clip); expandedView.setTextViewText(R.id.current_clip, MyUtil.stringLengthCut(currentClip)); //add pIntent for share Intent openShareIntent = new Intent(this.context, ClipObjectActionBridge.class) .putExtra(Intent.EXTRA_TEXT, currentClip) .putExtra(ClipObjectActionBridge.ACTION_CODE, ClipObjectActionBridge.ACTION_SHARE); PendingIntent pOpenShareIntent = PendingIntent.getService(this.context, buttonNumber++, openShareIntent, PendingIntent.FLAG_UPDATE_CURRENT); expandedView.setOnClickPendingIntent(R.id.clip_share_button, pOpenShareIntent); //add pIntent for edit Intent openEditIntent = new Intent(this.context, ClipObjectActionBridge.class) .putExtra(Intent.EXTRA_TEXT, currentClip) .putExtra(ClipObjectActionBridge.STATUE_IS_STARRED, clipObject.isStarred()) .putExtra(ClipObjectActionBridge.ACTION_CODE, ClipObjectActionBridge.ACTION_EDIT); PendingIntent pOpenEditIntent = PendingIntent.getService( this.context, buttonNumber++, openEditIntent, PendingIntent.FLAG_UPDATE_CURRENT); expandedView.setOnClickPendingIntent(R.id.current_clip, pOpenEditIntent); //add pIntent for star click Intent openStarIntent = new Intent(this.context, CBWatcherService.class) .putExtra(INTENT_EXTRA_CHANGE_STAR_STATUES, true); PendingIntent pOpenStarIntent = PendingIntent.getService( this.context, buttonNumber++, openStarIntent, PendingIntent.FLAG_UPDATE_CURRENT); expandedView.setOnClickPendingIntent(R.id.star, pOpenStarIntent); //set star's icon if (isStarred) { expandedView.setImageViewResource(R.id.star, R.drawable.ic_action_star_yellow); } else { expandedView.setImageViewResource(R.id.star, R.drawable.ic_action_star_outline_grey600); } expandedView.removeAllViews(R.id.notification_list); } public void addClips(ClipObject clipObject) { RemoteViews theClipView = new RemoteViews(context.getPackageName(), R.layout.notification_clip_card); if (clipObject.isStarred()) { theClipView.setTextViewText(R.id.clip_text, "★ " + MyUtil.stringLengthCut(clipObject.getText())); } else { theClipView.setTextViewText(R.id.clip_text, MyUtil.stringLengthCut(clipObject.getText())); } //add pIntent for edit Intent openEditIntent = new Intent(context, ClipObjectActionBridge.class) .putExtra(Intent.EXTRA_TEXT, clipObject.getText()) .putExtra(ClipObjectActionBridge.STATUE_IS_STARRED, clipObject.isStarred()) .putExtra(ClipObjectActionBridge.ACTION_CODE, ClipObjectActionBridge.ACTION_EDIT); PendingIntent pOpenEditIntent = PendingIntent.getService( context, buttonNumber++, openEditIntent, PendingIntent.FLAG_UPDATE_CURRENT); theClipView.setOnClickPendingIntent(R.id.clip_text, pOpenEditIntent); if (clipObject.getText().equals(getString(R.string.clip_notification_single_text))) { //hide copy button for 'add' theClipView.setTextViewText(R.id.clip_text, "✍ " + getString(R.string.clip_notification_single_text)); theClipView.setViewVisibility(R.id.notification_item_down_line, View.GONE); theClipView.setImageViewResource(R.id.clip_copy_button, temporaryStop ? R.drawable.ic_notification_action_play : R.drawable.ic_notification_action_pause ); Intent pauseIntent = new Intent(context, CBWatcherService.class) .putExtra(INTENT_EXTRA_TEMPORARY_STOP, !temporaryStop); PendingIntent pPauseIntent = PendingIntent.getService(context, buttonNumber++, pauseIntent, PendingIntent.FLAG_UPDATE_CURRENT ); theClipView.setOnClickPendingIntent(R.id.clip_copy_button, pPauseIntent); } else { clips.add(clipObject); //add pIntent for copy Intent openCopyIntent = new Intent(context, ClipObjectActionBridge.class) .putExtra(Intent.EXTRA_TEXT, clipObject.getText()) .putExtra(ClipObjectActionBridge.STATUE_IS_STARRED, true) .putExtra(ClipObjectActionBridge.ACTION_CODE, ClipObjectActionBridge.ACTION_COPY); PendingIntent pOpenCopyIntent = PendingIntent.getService(context, buttonNumber++, openCopyIntent, PendingIntent.FLAG_UPDATE_CURRENT); theClipView.setOnClickPendingIntent(R.id.clip_copy_button, pOpenCopyIntent); } expandedView.addView(R.id.notification_list, theClipView); } public List<Notification> getWearNotifications() { List<Notification> notifications = new ArrayList<>(); Intent openStarIntent = new Intent(this.context, CBWatcherService.class) .putExtra(INTENT_EXTRA_CHANGE_STAR_STATUES, true); PendingIntent pOpenStarIntent = PendingIntent.getService( this.context, buttonNumber++, openStarIntent, PendingIntent.FLAG_UPDATE_CURRENT); Intent openMainIntent = new Intent(context, ClipObjectActionBridge.class) .putExtra(ClipObjectActionBridge.ACTION_CODE, ClipObjectActionBridge.ACTION_OPEN_MAIN_DIALOG); PendingIntent pOpenMainIntent = PendingIntent.getService( context, pIntentId--, openMainIntent, PendingIntent.FLAG_UPDATE_CURRENT ); for (ClipObject clip : clips) { notifications.add(new NotificationCompat.Builder(mContext) //.setStyle(wearPageStyle) .setContentTitle( MyUtil.getFormatDate(context, clip.getDate()) + " " + MyUtil.getFormatTime(context, clip.getDate()) ) .setContentText(MyUtil.stringLengthCut(clip.getText(), 300)) .setSmallIcon(R.drawable.ic_stat_icon_colorful) .setGroup(NOTIFICATION_GROUP) .addAction( isStarred ? R.drawable.ic_action_star_outline_white : R.drawable.ic_action_star_white, isStarred ? getString(R.string.switch_all_items) : getString(R.string.switch_only_starred_items), pOpenStarIntent ) .addAction(R.drawable.ic_stat_icon, getString(R.string.app_name), pOpenMainIntent) .build()); } int size = notifications.size(); if (size > 4) { notifications = notifications.subList(0, 4); } Collections.reverse(notifications); return notifications; } public RemoteViews build() { //expandedView.setTextViewText(R.id.text, "Hello World!"); return expandedView; } } }