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" />
-
+
+