diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 827e4e3f4..5bc0835b3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -154,6 +154,14 @@ + + + + + + diff --git a/app/src/main/java/fr/neamar/kiss/MainActivity.java b/app/src/main/java/fr/neamar/kiss/MainActivity.java index d598bd97a..a1c838ce0 100644 --- a/app/src/main/java/fr/neamar/kiss/MainActivity.java +++ b/app/src/main/java/fr/neamar/kiss/MainActivity.java @@ -32,9 +32,10 @@ import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; +import androidx.annotation.NonNull; + import java.util.ArrayList; -import androidx.annotation.NonNull; import fr.neamar.kiss.adapter.RecordAdapter; import fr.neamar.kiss.broadcast.IncomingCallHandler; import fr.neamar.kiss.forwarder.ForwarderManager; @@ -114,7 +115,7 @@ public class MainActivity extends Activity implements QueryInterface, KeyboardSc * Favorites bar. Can be either the favorites within the KISS bar, * or the external favorites bar (default) */ - public View favoritesBar; + public ViewGroup favoritesBar; /** * Progress bar displayed when loading */ @@ -414,10 +415,11 @@ protected void onResume() { super.onResume(); } + @Override - protected void onDestroy() { - super.onDestroy(); - this.unregisterReceiver(this.mReceiver); + protected void onPause() { + super.onPause(); + forwarderManager.onPause(); } @Override @@ -426,6 +428,12 @@ protected void onStop() { forwarderManager.onStop(); } + @Override + protected void onDestroy() { + super.onDestroy(); + this.unregisterReceiver(this.mReceiver); + } + @Override protected void onNewIntent(Intent intent) { // This is called when the user press Home again while already browsing MainActivity diff --git a/app/src/main/java/fr/neamar/kiss/SettingsActivity.java b/app/src/main/java/fr/neamar/kiss/SettingsActivity.java index f5681df48..16097e360 100644 --- a/app/src/main/java/fr/neamar/kiss/SettingsActivity.java +++ b/app/src/main/java/fr/neamar/kiss/SettingsActivity.java @@ -14,7 +14,6 @@ import android.preference.MultiSelectListPreference; import android.preference.Preference; import android.preference.PreferenceActivity; -import android.preference.PreferenceCategory; import android.preference.PreferenceGroup; import android.preference.PreferenceManager; import android.preference.PreferenceScreen; @@ -106,6 +105,9 @@ protected void onCreate(Bundle savedInstanceState) { removePreference("history-hide-section", "pref-hide-navbar"); removePreference("history-hide-section", "pref-hide-statusbar"); } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { + removePreference("advanced", "enable-notifications"); + } addHiddenTagsTogglesInformation(prefs); } @@ -170,7 +172,7 @@ public void onClick(View v) { } private void removePreference(String parent, String category) { - PreferenceCategory p = (PreferenceCategory) findPreference(parent); + PreferenceGroup p = (PreferenceGroup) findPreference(parent); Preference c = findPreference(category); p.removePreference(c); } diff --git a/app/src/main/java/fr/neamar/kiss/UIColors.java b/app/src/main/java/fr/neamar/kiss/UIColors.java index 908cca353..3290a4eda 100644 --- a/app/src/main/java/fr/neamar/kiss/UIColors.java +++ b/app/src/main/java/fr/neamar/kiss/UIColors.java @@ -10,6 +10,8 @@ import android.view.Window; import android.view.WindowManager; +import androidx.annotation.NonNull; + public class UIColors { public static final int COLOR_DEFAULT = 0xFF4caf50; // Source: https://material.io/guidelines/style/color.html#color-color-palette @@ -71,7 +73,7 @@ public static int getPrimaryColor(Context context) { return primaryColor; } - public static void clearPrimaryColorCache(Context context) { + static void clearPrimaryColorCache(Context context) { primaryColor = -1; } } diff --git a/app/src/main/java/fr/neamar/kiss/adapter/RecordAdapter.java b/app/src/main/java/fr/neamar/kiss/adapter/RecordAdapter.java index c8236c9e9..56bbb3b12 100644 --- a/app/src/main/java/fr/neamar/kiss/adapter/RecordAdapter.java +++ b/app/src/main/java/fr/neamar/kiss/adapter/RecordAdapter.java @@ -7,13 +7,14 @@ import android.widget.BaseAdapter; import android.widget.SectionIndexer; +import androidx.annotation.NonNull; + import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Set; -import androidx.annotation.NonNull; import fr.neamar.kiss.KissApplication; import fr.neamar.kiss.normalizer.StringNormalizer; import fr.neamar.kiss.result.AppResult; diff --git a/app/src/main/java/fr/neamar/kiss/forwarder/Favorites.java b/app/src/main/java/fr/neamar/kiss/forwarder/Favorites.java index f87220f86..44c5feaeb 100644 --- a/app/src/main/java/fr/neamar/kiss/forwarder/Favorites.java +++ b/app/src/main/java/fr/neamar/kiss/forwarder/Favorites.java @@ -2,13 +2,16 @@ import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Build; import android.provider.ContactsContract; import android.util.Log; import android.view.DragEvent; @@ -19,13 +22,18 @@ import android.view.ViewGroup; import android.widget.ImageView; +import androidx.annotation.NonNull; + import java.util.ArrayList; import fr.neamar.kiss.KissApplication; import fr.neamar.kiss.MainActivity; import fr.neamar.kiss.R; +import fr.neamar.kiss.UIColors; import fr.neamar.kiss.db.DBHelper; +import fr.neamar.kiss.notification.NotificationListener; import fr.neamar.kiss.pojo.Pojo; +import fr.neamar.kiss.result.AppResult; import fr.neamar.kiss.result.ContactsResult; import fr.neamar.kiss.result.Result; import fr.neamar.kiss.ui.ListPopup; @@ -40,7 +48,7 @@ public class Favorites extends Forwarder implements View.OnClickListener, View.O /** * IDs for the favorites buttons */ - private ArrayList favoritesViews = new ArrayList<>(); + private ArrayList favoritesViews = new ArrayList<>(); /** * Currently displayed favorites @@ -66,6 +74,8 @@ public class Favorites extends Forwarder implements View.OnClickListener, View.O private boolean contextMenuShown = false; private int favCount = -1; + private SharedPreferences notificationPrefs = null; + Favorites(MainActivity mainActivity) { super(mainActivity); } @@ -89,6 +99,11 @@ void onCreate() { // set flag to false prefs.edit().putBoolean("firstRun", false).apply(); } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + notificationPrefs = mainActivity.getSharedPreferences(NotificationListener.NOTIFICATION_PREFERENCES_NAME, Context.MODE_PRIVATE); + } + } private static Bitmap drawableToBitmap(Drawable drawable) { @@ -120,11 +135,17 @@ void onFavoriteChange() { favCount = favoritesPojo.size(); LayoutInflater layoutInflater = null; + int dotColor; + if (isExternalFavoriteBarEnabled()) { + dotColor = UIColors.getPrimaryColor(mainActivity); + } + else { + dotColor = Color.WHITE; + } + // Don't look for items after favIds length, we won't be able to display them for (int i = 0; i < favoritesPojo.size(); i++) { - Pojo pojo = favoritesPojo.get(i); - - ImageView image; + View favoriteView; if (favoritesViews.size() <= i) { if (layoutInflater == null) { @@ -132,31 +153,49 @@ void onFavoriteChange() { .getSystemService(Context.LAYOUT_INFLATER_SERVICE); } assert layoutInflater != null; - image = (ImageView) layoutInflater.inflate(R.layout.favorite_item, (ViewGroup) mainActivity.favoritesBar, false); - image.setTag(i); - image.setOnDragListener(this); - image.setOnTouchListener(this); - ((ViewGroup) mainActivity.favoritesBar).addView(image); - favoritesViews.add(image); + favoriteView = layoutInflater.inflate(R.layout.favorite_item, (ViewGroup) mainActivity.favoritesBar, false); + favoriteView.setTag(i); + favoriteView.setOnDragListener(this); + favoriteView.setOnTouchListener(this); + mainActivity.favoritesBar.addView(favoriteView); + favoritesViews.add(favoriteView); } else { - image = (ImageView) favoritesViews.get(i); + favoriteView = favoritesViews.get(i); } + Pojo pojo = favoritesPojo.get(i); Result result = Result.fromPojo(mainActivity, pojo); Drawable drawable = result.getDrawable(mainActivity); + ImageView favoriteImage = favoriteView.findViewById(R.id.favorite); if (drawable != null) { if (result instanceof ContactsResult) { Bitmap iconBitmap = drawableToBitmap(drawable); drawable = new RoundedQuickContactBadge.RoundedDrawable(iconBitmap); } - image.setImageDrawable(drawable); + favoriteImage.setImageDrawable(drawable); } else { // Use a placeholder if no drawable found - image.setImageResource(R.drawable.ic_launcher_white); + favoriteImage.setImageResource(R.drawable.ic_launcher_white); } - image.setVisibility(View.VISIBLE); - image.setContentDescription(pojo.getName()); + if (notificationPrefs != null) { + ImageView notificationDot = favoriteView.findViewById(R.id.item_notification_dot); + notificationDot.setColorFilter(dotColor); + + if (result instanceof AppResult) { + String packageName = ((AppResult) result).getPackageName(); + notificationDot.setTag(packageName); + notificationDot.setVisibility(notificationPrefs.contains(packageName) ? View.VISIBLE : View.GONE); + } else { + // Ensure view is clean (might have been recycled after a drag and drop) + notificationDot.setTag(null); + notificationDot.setVisibility(View.GONE); + } + + } + + favoriteView.setVisibility(View.VISIBLE); + favoriteView.setContentDescription(pojo.getName()); } // Hide empty favorites (not enough favorites yet) @@ -177,7 +216,7 @@ void updateSearchRecords(String query) { void onDataSetChanged() { // Do not display the external bar when viewing all apps - if(mainActivity.isViewingAllApps() && isExternalFavoriteBarEnabled()) { + if (mainActivity.isViewingAllApps() && isExternalFavoriteBarEnabled()) { mainActivity.favoritesBar.setVisibility(View.GONE); } } @@ -187,7 +226,7 @@ void onDataSetChanged() { */ private void addDefaultAppsToFavs() { { - //Default phone call app + // Default phone call app Intent phoneIntent = new Intent(Intent.ACTION_DIAL); phoneIntent.setData(Uri.parse("tel:0000")); ResolveInfo resolveInfo = mainActivity.getPackageManager().resolveActivity(phoneIntent, PackageManager.MATCH_DEFAULT_ONLY); @@ -197,7 +236,7 @@ private void addDefaultAppsToFavs() { if (resolveInfo.activityInfo.name != null && !resolveInfo.activityInfo.name.equals(DEFAULT_RESOLVER)) { String activityName = resolveInfo.activityInfo.name; - if(packageName.equals("com.google.android.dialer")) { + if (packageName.equals("com.google.android.dialer")) { // Default dialer has two different activities, one when calling a phone number and one when opening the app from the launcher. // (com.google.android.apps.dialer.extensions.GoogleDialtactsActivity vs. com.google.android.dialer.extensions.GoogleDialtactsActivity) // (notice the .apps. in the middle) @@ -212,7 +251,7 @@ private void addDefaultAppsToFavs() { } } { - //Default contacts app + // Default contacts app Intent contactsIntent = new Intent(Intent.ACTION_DEFAULT, ContactsContract.Contacts.CONTENT_URI); ResolveInfo resolveInfo = mainActivity.getPackageManager().resolveActivity(contactsIntent, PackageManager.MATCH_DEFAULT_ONLY); if (resolveInfo != null) { @@ -225,7 +264,7 @@ private void addDefaultAppsToFavs() { } { - //Default browser + // Default browser Intent browserIntent = new Intent("android.intent.action.VIEW", Uri.parse("http://")); ResolveInfo resolveInfo = mainActivity.getPackageManager().resolveActivity(browserIntent, PackageManager.MATCH_DEFAULT_ONLY); if (resolveInfo != null) { @@ -234,7 +273,7 @@ private void addDefaultAppsToFavs() { if (resolveInfo.activityInfo.name != null && !resolveInfo.activityInfo.name.equals(DEFAULT_RESOLVER)) { String activityName = resolveInfo.activityInfo.name; - if(packageName.equalsIgnoreCase("com.android.chrome")) { + if (packageName.equalsIgnoreCase("com.android.chrome")) { // Chrome has two different activities, one for Launcher and one when opening an URL. // The browserIntent above resolve to the latter, which isn't exposed as a Launcher activity and thus can't be displayed // This hack uses the correct resolver when the application is Chrome. @@ -248,13 +287,8 @@ private void addDefaultAppsToFavs() { } } + @NonNull private Result getFavResult(int favNumber) { - if (favNumber >= favoritesPojo.size()) { - // Clicking on a favorite before everything is loaded. - Log.i(TAG, "Clicking on an uninitialized favorite."); - return null; - } - // Favorites handling Pojo pojo = favoritesPojo.get(favNumber); return Result.fromPojo(mainActivity, pojo); @@ -292,7 +326,7 @@ public boolean onTouch(View view, MotionEvent motionEvent) { return true; } // No need to do the extra work - if(isDragging) { + if (isDragging) { return true; } @@ -302,7 +336,7 @@ public boolean onTouch(View view, MotionEvent motionEvent) { this.onClick(view); return true; } - if(!contextMenuShown && holdTime > LONG_PRESS_DELAY) { + if (!contextMenuShown && holdTime > LONG_PRESS_DELAY) { contextMenuShown = true; this.onLongClick(view); return true; @@ -352,7 +386,7 @@ public boolean onDrag(View v, final DragEvent event) { case DragEvent.ACTION_DRAG_ENDED: // Only need to handle this action once. - if(!isDragging) { + if (!isDragging) { return true; } isDragging = false; @@ -386,8 +420,8 @@ public void run() { final int pos = KissApplication.getApplication(mainActivity).getDataHandler().getFavoritePosition(mainActivity, overApp.id); draggedView.post(new Runnable() { - @Override - public void run() { + @Override + public void run() { // Signals to a View that the drag and drop operation has concluded. // If event result is set, this means the dragged view was dropped in target if (event.getResult()) { diff --git a/app/src/main/java/fr/neamar/kiss/forwarder/ForwarderManager.java b/app/src/main/java/fr/neamar/kiss/forwarder/ForwarderManager.java index 26644de21..0ab8408f1 100644 --- a/app/src/main/java/fr/neamar/kiss/forwarder/ForwarderManager.java +++ b/app/src/main/java/fr/neamar/kiss/forwarder/ForwarderManager.java @@ -7,6 +7,7 @@ import android.view.View; import androidx.annotation.NonNull; + import fr.neamar.kiss.MainActivity; public class ForwarderManager extends Forwarder { @@ -18,6 +19,7 @@ public class ForwarderManager extends Forwarder { private final Permission permissionForwarder; private final OreoShortcuts shortcutsForwarder; private final TagsMenu tagsMenu; + private final Notification notificationForwarder; public ForwarderManager(MainActivity mainActivity) { @@ -30,6 +32,7 @@ public ForwarderManager(MainActivity mainActivity) { this.favoritesForwarder = new Favorites(mainActivity); this.permissionForwarder = new Permission(mainActivity); this.shortcutsForwarder = new OreoShortcuts(mainActivity); + this.notificationForwarder = new Notification(mainActivity); this.tagsMenu = new TagsMenu(mainActivity); } @@ -40,16 +43,22 @@ public void onCreate() { experienceTweaks.onCreate(); shortcutsForwarder.onCreate(); tagsMenu.onCreate(); + + } + + public void onStart() { + widgetForwarder.onStart(); } public void onResume() { interfaceTweaks.onResume(); experienceTweaks.onResume(); + notificationForwarder.onResume(); tagsMenu.onResume(); } - public void onStart() { - widgetForwarder.onStart(); + public void onPause() { + notificationForwarder.onPause(); } public void onStop() { diff --git a/app/src/main/java/fr/neamar/kiss/forwarder/Notification.java b/app/src/main/java/fr/neamar/kiss/forwarder/Notification.java new file mode 100644 index 000000000..28bdac767 --- /dev/null +++ b/app/src/main/java/fr/neamar/kiss/forwarder/Notification.java @@ -0,0 +1,96 @@ +package fr.neamar.kiss.forwarder; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.content.SharedPreferences; +import android.os.Build; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListView; + +import fr.neamar.kiss.MainActivity; +import fr.neamar.kiss.R; +import fr.neamar.kiss.notification.NotificationListener; + +import static android.content.Context.MODE_PRIVATE; + +class Notification extends Forwarder { + private final SharedPreferences notificationPreferences; + + private SharedPreferences.OnSharedPreferenceChangeListener onNotificationDisplayed = new SharedPreferences.OnSharedPreferenceChangeListener() { + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String packageName) { + ListView list = mainActivity.list; + + // A new notification was received, iterate over the currently displayed results + // if one of them is for the package that just received a notification, + // update the notification dot visual if required. + // + // This implementation should be more efficient than calling notifyDataSetInvalidated() + // since we only iterate over the items currently displayed in the list + // and do not rebuild them all, just toggle visibility if required. + // Also, it means we get to display an animation, and that's cool :D + updateDots(list, list.getLastVisiblePosition() - list.getFirstVisiblePosition(), packageName); + + updateDots(mainActivity.favoritesBar, mainActivity.favoritesBar.getChildCount(), packageName); + + } + }; + + Notification(MainActivity mainActivity) { + super(mainActivity); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + notificationPreferences = mainActivity.getSharedPreferences(NotificationListener.NOTIFICATION_PREFERENCES_NAME, MODE_PRIVATE); + } else { + notificationPreferences = null; + } + } + + void onResume() { + if (notificationPreferences != null) { + notificationPreferences.registerOnSharedPreferenceChangeListener(onNotificationDisplayed); + } + } + + void onPause() { + if (notificationPreferences != null) { + notificationPreferences.unregisterOnSharedPreferenceChangeListener(onNotificationDisplayed); + } + } + + private void updateDots(ViewGroup vg, int childCount, String packageName) { + for (int i = 0; i < childCount; i++) { + View v = vg.getChildAt(i); + final View notificationDot = v.findViewById(R.id.item_notification_dot); + if (notificationDot != null && packageName.equals(notificationDot.getTag())) { + boolean hasNotification = notificationPreferences.contains(packageName); + animateDot(notificationDot, hasNotification); + } + } + } + + private void animateDot(final View notificationDot, Boolean hasNotification) { + int currentVisibility = notificationDot.getVisibility(); + + if(currentVisibility != View.VISIBLE && hasNotification) { + // There is a notification and dot was not visible + notificationDot.setVisibility(View.VISIBLE); + notificationDot.setScaleX(0); + notificationDot.setScaleY(0); + notificationDot.animate().scaleX(1).scaleY(1).setListener(null); + } + else if(currentVisibility == View.VISIBLE && !hasNotification) { + // There is no notification anymore, and dot was visible + notificationDot.animate().scaleX(0).scaleY(0).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + notificationDot.setVisibility(View.GONE); + notificationDot.setScaleX(1); + notificationDot.setScaleY(1); + } + }); + } + } +} diff --git a/app/src/main/java/fr/neamar/kiss/notification/NotificationListener.java b/app/src/main/java/fr/neamar/kiss/notification/NotificationListener.java new file mode 100644 index 000000000..0be25347d --- /dev/null +++ b/app/src/main/java/fr/neamar/kiss/notification/NotificationListener.java @@ -0,0 +1,121 @@ +package fr.neamar.kiss.notification; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Build; +import android.service.notification.NotificationListenerService; +import android.service.notification.StatusBarNotification; +import android.util.Log; + +import androidx.annotation.RequiresApi; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) +public class NotificationListener extends NotificationListenerService { + public static final String TAG = "NotifListener"; + public static final String NOTIFICATION_PREFERENCES_NAME = "notifications"; + + private SharedPreferences prefs; + + @Override + public void onCreate() { + super.onCreate(); + prefs = getBaseContext().getSharedPreferences(NOTIFICATION_PREFERENCES_NAME, Context.MODE_PRIVATE); + } + + @Override + public void onListenerConnected() { + super.onListenerConnected(); + Log.i(TAG, "Notification listener connected"); + + // Build a map of notifications currently displayed, + // ordered per package + StatusBarNotification[] sbns = getActiveNotifications(); + Map> notificationsByPackage = new HashMap<>(); + for (StatusBarNotification sbn : sbns) { + String packageName = sbn.getPackageName(); + if (!notificationsByPackage.containsKey(packageName)) { + notificationsByPackage.put(packageName, new HashSet()); + } + + notificationsByPackage.get(packageName).add(Integer.toString(sbn.getId())); + } + + // And synchronise this map with our SharedPreferences + // (an easier option would have been to .clear() the SharedPreferences, + // but then the listeners on SharedPreferences are not properly triggered) + SharedPreferences.Editor editor = prefs.edit(); + // allKeys contains all the package names either in preferences or in the current notifications + Set allKeys = new HashSet<>(prefs.getAll().keySet()); + allKeys.addAll(notificationsByPackage.keySet()); + for (String packageName : allKeys) { + if (notificationsByPackage.containsKey(packageName)) { + editor.putStringSet(packageName, notificationsByPackage.get(packageName)); + } else { + editor.remove(packageName); + } + } + + editor.apply(); + } + + @Override + public void onListenerDisconnected() { + Log.i(TAG, "Notification listener disconnected"); + + // Clean up everything we have in memory to ensure we don't keep displaying trailing dots. + // We don't use .clear() to ensure listeners are properly called. + SharedPreferences.Editor editor = prefs.edit(); + Set packages = prefs.getAll().keySet(); + + for (String packageName : packages) { + editor.remove(packageName); + } + editor.apply(); + + super.onListenerDisconnected(); + } + + @Override + public void onNotificationPosted(StatusBarNotification sbn) { + Set currentNotifications = getCurrentNotificationsForPackage(sbn.getPackageName()); + + currentNotifications.add(Integer.toString(sbn.getId())); + prefs.edit().putStringSet(sbn.getPackageName(), currentNotifications).apply(); + + Log.v(TAG, "Added notification for " + sbn.getPackageName() + ": " + currentNotifications.toString()); + } + + @Override + public void onNotificationRemoved(StatusBarNotification sbn) { + Set currentNotifications = getCurrentNotificationsForPackage(sbn.getPackageName()); + + currentNotifications.remove(Integer.toString(sbn.getId())); + + SharedPreferences.Editor editor = prefs.edit(); + if (currentNotifications.isEmpty()) { + // Clean up! + editor.remove(sbn.getPackageName()); + } else { + editor.putStringSet(sbn.getPackageName(), currentNotifications); + } + editor.apply(); + + Log.v(TAG, "Removed notification for " + sbn.getPackageName() + ": " + currentNotifications.toString()); + } + + public Set getCurrentNotificationsForPackage(String packageName) { + Set currentNotifications = prefs.getStringSet(packageName, null); + if (currentNotifications == null) { + return new HashSet<>(); + } else { + // The set returned by getStringSet() should NOT be modified + // see https://developer.android.com/reference/android/content/SharedPreferences.html#getStringSet(java.lang.String,%2520java.util.Set%3Cjava.lang.String%3E) + return new HashSet<>(currentNotifications); + } + } +} diff --git a/app/src/main/java/fr/neamar/kiss/preference/NotificationPreference.java b/app/src/main/java/fr/neamar/kiss/preference/NotificationPreference.java new file mode 100644 index 000000000..cf73c6761 --- /dev/null +++ b/app/src/main/java/fr/neamar/kiss/preference/NotificationPreference.java @@ -0,0 +1,23 @@ +package fr.neamar.kiss.preference; + +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.preference.DialogPreference; +import android.util.AttributeSet; + +public class NotificationPreference extends DialogPreference { + private static final String ACTION_NOTIFICATION_LISTENER_SETTINGS = "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"; + + public NotificationPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + super.onClick(dialog, which); + if (which == DialogInterface.BUTTON_POSITIVE) { + getContext().startActivity(new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS)); + } + } +} diff --git a/app/src/main/java/fr/neamar/kiss/result/AppResult.java b/app/src/main/java/fr/neamar/kiss/result/AppResult.java index ec0584e8f..742a6beec 100644 --- a/app/src/main/java/fr/neamar/kiss/result/AppResult.java +++ b/app/src/main/java/fr/neamar/kiss/result/AppResult.java @@ -30,7 +30,9 @@ import fr.neamar.kiss.KissApplication; import fr.neamar.kiss.MainActivity; import fr.neamar.kiss.R; +import fr.neamar.kiss.UIColors; import fr.neamar.kiss.adapter.RecordAdapter; +import fr.neamar.kiss.notification.NotificationListener; import fr.neamar.kiss.pojo.AppPojo; import fr.neamar.kiss.ui.GoogleCalendarIcon; import fr.neamar.kiss.ui.ListPopup; @@ -82,9 +84,24 @@ public View display(final Context context, int position, View convertView, Fuzzy } else { appIcon.setImageDrawable(null); } + + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + SharedPreferences notificationPrefs = context.getSharedPreferences(NotificationListener.NOTIFICATION_PREFERENCES_NAME, Context.MODE_PRIVATE); + ImageView notificationView = view.findViewById(R.id.item_notification_dot); + notificationView.setVisibility(notificationPrefs.contains(getPackageName()) ? View.VISIBLE : View.GONE); + notificationView.setTag(getPackageName()); + + int primaryColor = UIColors.getPrimaryColor(context); + notificationView.setColorFilter(primaryColor); + } + return view; } + public String getPackageName() { + return appPojo.packageName; + } + @Override protected ListPopup buildPopupMenu(Context context, ArrayAdapter adapter, final RecordAdapter parent, View parentView) { if (!(context instanceof MainActivity) || ((MainActivity) context).isViewingSearchResults()) { diff --git a/app/src/main/java/fr/neamar/kiss/result/ContactsResult.java b/app/src/main/java/fr/neamar/kiss/result/ContactsResult.java index 49c048655..c0bceb123 100644 --- a/app/src/main/java/fr/neamar/kiss/result/ContactsResult.java +++ b/app/src/main/java/fr/neamar/kiss/result/ContactsResult.java @@ -174,7 +174,6 @@ void setDrawableCache(Drawable drawable) { icon = drawable; } - @SuppressWarnings("deprecation") @Override public Drawable getDrawable(Context context) { synchronized (this) { @@ -203,7 +202,7 @@ public Drawable getDrawable(Context context) { } } - public void launchContactView(Context context, View v) { + private void launchContactView(Context context, View v) { Intent viewContact = new Intent(Intent.ACTION_VIEW); viewContact.setData(Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, diff --git a/app/src/main/res/drawable/notification_dot.xml b/app/src/main/res/drawable/notification_dot.xml new file mode 100644 index 000000000..dd8c9c62e --- /dev/null +++ b/app/src/main/res/drawable/notification_dot.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/favorite_item.xml b/app/src/main/res/layout/favorite_item.xml index 79959d5e2..4bc892932 100644 --- a/app/src/main/res/layout/favorite_item.xml +++ b/app/src/main/res/layout/favorite_item.xml @@ -1,11 +1,28 @@ - + tools:layout_width="match_parent"> + + + + + diff --git a/app/src/main/res/layout/item_app.xml b/app/src/main/res/layout/item_app.xml index 7747c6fea..b367e8b94 100644 --- a/app/src/main/res/layout/item_app.xml +++ b/app/src/main/res/layout/item_app.xml @@ -1,37 +1,48 @@ - + android:paddingBottom="@dimen/result_margin_bottom"> - + android:layout_marginEnd="@dimen/icon_margin_right" + android:layout_marginRight="@dimen/icon_margin_right" + android:layout_marginBottom="@dimen/icon_margin_bottom"> + + + + @@ -41,9 +52,9 @@ android:layout_height="wrap_content" android:ellipsize="end" android:maxLines="1" - android:paddingBottom="3dp" android:paddingEnd="2dp" android:paddingRight="2dp" + android:paddingBottom="3dp" android:shadowColor="?attr/resultShadowColor" android:shadowDx="1" android:shadowDy="2" @@ -71,4 +82,4 @@ tools:text="@string/stub_app_tag" /> - + diff --git a/app/src/main/res/layout/main.xml b/app/src/main/res/layout/main.xml index d86796435..49f6a25c6 100644 --- a/app/src/main/res/layout/main.xml +++ b/app/src/main/res/layout/main.xml @@ -22,7 +22,7 @@ android:id="@+id/resultLayout" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_above="@+id/externalFavoriteBar" + android:layout_above="@id/externalFavoriteBar" android:layout_alignParentStart="true" android:layout_alignParentLeft="true" android:layout_alignParentTop="false" @@ -60,7 +60,7 @@ android:id="@+id/externalFavoriteBar" android:layout_width="match_parent" android:layout_height="58dp" - android:layout_above="@+id/searchEditLayout" + android:layout_above="@id/searchEditLayout" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:layout_marginBottom="10dp" @@ -124,10 +124,10 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_centerVertical="true" - android:layout_toStartOf="@+id/rightHandSideButtonsWrapper" - android:layout_toLeftOf="@+id/rightHandSideButtonsWrapper" - android:layout_toEndOf="@+id/leftHandSideButtonsWrapper" - android:layout_toRightOf="@+id/leftHandSideButtonsWrapper" + android:layout_toStartOf="@id/rightHandSideButtonsWrapper" + android:layout_toLeftOf="@id/rightHandSideButtonsWrapper" + android:layout_toEndOf="@id/leftHandSideButtonsWrapper" + android:layout_toRightOf="@id/leftHandSideButtonsWrapper" android:layout_margin="1dp" android:background="?attr/searchBackgroundColor" android:hint="@string/ui_search_hint" diff --git a/app/src/main/res/values-v18/bools.xml b/app/src/main/res/values-v18/bools.xml new file mode 100644 index 000000000..35ea192b7 --- /dev/null +++ b/app/src/main/res/values-v18/bools.xml @@ -0,0 +1,4 @@ + + + true + \ No newline at end of file diff --git a/app/src/main/res/values/bools.xml b/app/src/main/res/values/bools.xml new file mode 100644 index 000000000..0ad3620a9 --- /dev/null +++ b/app/src/main/res/values/bools.xml @@ -0,0 +1,4 @@ + + + false + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index de9258539..a15bdc37d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -214,4 +214,6 @@ Unable to initialize shortcuts. Is KISS your default launcher? Enable gestures on empty areas (↑ for keyboard, ↓ for notifications) + Display a dot on apps with notifications + To enable or disable this feature, check or uncheck KISS in the following screen. \ No newline at end of file diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index be0b8b5b1..830492556 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -289,7 +289,12 @@ android:title="@string/search_provider_reset" /> - + +