diff --git a/app/build.gradle b/app/build.gradle
index df92305c90..73c62f085b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -14,7 +14,7 @@ plugins {
android {
defaultConfig {
- compileSdk 34
+ compileSdk 35
}
buildFeatures {
buildConfig = true
@@ -22,7 +22,7 @@ android {
defaultConfig {
applicationId 'fr.neamar.kiss'
minSdkVersion 15
- targetSdkVersion 34
+ targetSdkVersion 35
versionCode 211
versionName "3.21.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index aa3cc6d41c..ab8ba4686a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -17,6 +17,8 @@
-->
+
+
+
diff --git a/app/src/main/java/fr/neamar/kiss/MainActivity.java b/app/src/main/java/fr/neamar/kiss/MainActivity.java
index 01cac3ed56..02b797884e 100644
--- a/app/src/main/java/fr/neamar/kiss/MainActivity.java
+++ b/app/src/main/java/fr/neamar/kiss/MainActivity.java
@@ -11,6 +11,8 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.LauncherApps;
+import android.content.pm.LauncherUserInfo;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -19,6 +21,8 @@
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.text.Editable;
@@ -44,8 +48,11 @@
import android.widget.TextView.OnEditorActionListener;
import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
import fr.neamar.kiss.adapter.RecordAdapter;
import fr.neamar.kiss.broadcast.IncomingCallHandler;
@@ -213,6 +220,11 @@ public void onReceive(Context context, Intent intent) {
// Run GC once to free all the garbage accumulated during provider initialization
System.gc();
+ } else if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ if (intent.getAction().equalsIgnoreCase(Intent.ACTION_PROFILE_AVAILABLE)
+ || intent.getAction().equalsIgnoreCase(Intent.ACTION_PROFILE_UNAVAILABLE)) {
+ privateSpaceStateEvent(intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle.class));
+ }
}
// New provider might mean new favorites
@@ -228,6 +240,12 @@ public void onReceive(Context context, Intent intent) {
this.registerReceiver(mReceiver, intentFilterLoad, Context.RECEIVER_EXPORTED);
this.registerReceiver(mReceiver, intentFilterLoadOver, Context.RECEIVER_EXPORTED);
this.registerReceiver(mReceiver, intentFilterFullLoadOver, Context.RECEIVER_EXPORTED);
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ IntentFilter intentFilterProfileAvailable = new IntentFilter(Intent.ACTION_PROFILE_AVAILABLE);
+ IntentFilter intentFilterProfileUnAvailable = new IntentFilter(Intent.ACTION_PROFILE_UNAVAILABLE);
+ this.registerReceiver(mReceiver, intentFilterProfileAvailable, Context.RECEIVER_EXPORTED);
+ this.registerReceiver(mReceiver, intentFilterProfileUnAvailable, Context.RECEIVER_EXPORTED);
+ }
}
else {
this.registerReceiver(mReceiver, intentFilterLoad);
@@ -402,6 +420,19 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMen
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_main, menu);
+
+ MenuItem privateSpaceItem = menu.findItem(R.id.private_space);
+ if (privateSpaceItem != null) {
+ if ((android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ || (getPrivateUser() == null)) {
+ privateSpaceItem.setVisible(false);
+ } else if (isPrivateSpaceUnlocked()) {
+ privateSpaceItem.setTitle("Lock Private Space");
+ } else {
+ privateSpaceItem.setTitle("Unlock Private Space");
+ }
+ }
+
forwarderManager.onCreateContextMenu(menu);
}
@@ -557,6 +588,9 @@ public boolean onOptionsItemSelected(MenuItem item) {
} else if (itemId == R.id.preferences) {
startActivity(new Intent(this, SettingsActivity.class));
return true;
+ } else if (itemId == R.id.private_space) {
+ switchPrivateSpaceState();
+ return true;
}
return super.onOptionsItemSelected(item);
}
@@ -566,7 +600,6 @@ public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_main, menu);
-
return true;
}
@@ -689,6 +722,64 @@ public void onAnimationEnd(Animator animation) {
}
}
+ @RequiresApi(35)
+ private UserHandle getPrivateUser() {
+ final UserManager manager = (UserManager) this.getSystemService(Context.USER_SERVICE);
+ assert manager != null;
+
+ final LauncherApps launcher = (LauncherApps) this.getSystemService(Context.LAUNCHER_APPS_SERVICE);
+ assert launcher != null;
+
+ List users = launcher.getProfiles();
+
+ UserHandle privateUser = null;
+ for (UserHandle user : users) {
+ if (Objects.requireNonNull(launcher.getLauncherUserInfo(user)).getUserType().equalsIgnoreCase(UserManager.USER_TYPE_PROFILE_PRIVATE)) {
+ privateUser = user;
+ break;
+ }
+ }
+ return privateUser;
+ }
+
+ private boolean isPrivateSpaceUnlocked() {
+ if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ return false;
+ }
+
+ final UserManager manager = (UserManager) this.getSystemService(Context.USER_SERVICE);
+ assert manager != null;
+
+ UserHandle user = getPrivateUser();
+ return !manager.isQuietModeEnabled(user);
+ }
+
+ @RequiresApi(35)
+ private void switchPrivateSpaceState() {
+ final UserManager manager = (UserManager) this.getSystemService(Context.USER_SERVICE);
+ assert manager != null;
+
+ UserHandle user = getPrivateUser();
+ manager.requestQuietModeEnabled(!manager.isQuietModeEnabled(user), user);
+ }
+
+ @RequiresApi(35)
+ private void privateSpaceStateEvent(UserHandle handle) {
+ if (handle == null) {
+ return;
+ }
+
+ final LauncherApps launcher = (LauncherApps) this.getSystemService(Context.LAUNCHER_APPS_SERVICE);
+
+ LauncherUserInfo info = launcher.getLauncherUserInfo(handle);
+ if (info != null) {
+ if (info.getUserType().equalsIgnoreCase(UserManager.USER_TYPE_PROFILE_PRIVATE)) {
+ Log.d(TAG, "Private Space state changed");
+ // TODO: Check if private space state changed and change app view accordingly
+ }
+ }
+ }
+
public void onFavoriteChange() {
forwarderManager.onFavoriteChange();
}
diff --git a/app/src/main/java/fr/neamar/kiss/SettingsActivity.java b/app/src/main/java/fr/neamar/kiss/SettingsActivity.java
index ea7b3bd10c..16727e6c59 100644
--- a/app/src/main/java/fr/neamar/kiss/SettingsActivity.java
+++ b/app/src/main/java/fr/neamar/kiss/SettingsActivity.java
@@ -672,6 +672,8 @@ public void onDenied() {
}
} else if ("selected-contact-mime-types".equals(key)) {
getDataHandler().reloadContactsProvider();
+ } else if ("disable-private-space-shortcuts".equals(key)) {
+ getDataHandler().reloadShortcuts();
}
}
diff --git a/app/src/main/java/fr/neamar/kiss/loader/LoadAppPojos.java b/app/src/main/java/fr/neamar/kiss/loader/LoadAppPojos.java
index 2e26f40182..5c1466c2cc 100644
--- a/app/src/main/java/fr/neamar/kiss/loader/LoadAppPojos.java
+++ b/app/src/main/java/fr/neamar/kiss/loader/LoadAppPojos.java
@@ -5,6 +5,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
+import android.content.pm.LauncherUserInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Build;
@@ -55,6 +56,10 @@ protected List doInBackground(Void... params) {
// Handle multi-profile support introduced in Android 5 (#542)
for (android.os.UserHandle profile : manager.getUserProfiles()) {
+ LauncherUserInfo info = null;
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ info = launcherApps.getLauncherUserInfo(profile);
+ }
UserHandle user = new UserHandle(manager.getSerialNumberForUser(profile), profile);
for (LauncherActivityInfo activityInfo : launcherApps.getActivityList(null, profile)) {
if (isCancelled()) {
@@ -63,7 +68,18 @@ protected List doInBackground(Void... params) {
ApplicationInfo appInfo = activityInfo.getApplicationInfo();
boolean disabled = PackageManagerUtils.isAppSuspended(appInfo) || isQuietModeEnabled(manager, profile);
final AppPojo app = createPojo(user, appInfo.packageName, activityInfo.getName(), activityInfo.getLabel(), disabled, excludedAppList, excludedFromHistoryAppList, excludedShortcutsAppList);
- apps.add(app);
+ if ((android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ && (info != null)) {
+ if (!info.getUserType().equalsIgnoreCase(UserManager.USER_TYPE_PROFILE_PRIVATE)) {
+ apps.add(app);
+ } else {
+ if (!isQuietModeEnabled(manager, profile)) {
+ apps.add(app);
+ }
+ }
+ } else {
+ apps.add(app);
+ }
}
}
} else {
diff --git a/app/src/main/java/fr/neamar/kiss/loader/LoadShortcutsPojos.java b/app/src/main/java/fr/neamar/kiss/loader/LoadShortcutsPojos.java
index 27db84c761..e755c470cd 100644
--- a/app/src/main/java/fr/neamar/kiss/loader/LoadShortcutsPojos.java
+++ b/app/src/main/java/fr/neamar/kiss/loader/LoadShortcutsPojos.java
@@ -2,9 +2,15 @@
package fr.neamar.kiss.loader;
import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.LauncherApps;
+import android.content.pm.LauncherUserInfo;
import android.content.pm.ShortcutInfo;
import android.os.Build;
import android.os.UserManager;
+import android.preference.PreferenceManager;
+
+import androidx.annotation.RequiresApi;
import java.util.ArrayList;
import java.util.List;
@@ -33,6 +39,8 @@ protected List doInBackground(Void... params) {
return new ArrayList<>();
}
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+
List records = DBHelper.getShortcuts(context);
DataHandler dataHandler = KissApplication.getApplication(context).getDataHandler();
TagsHandler tagsHandler = dataHandler.getTagsHandler();
@@ -52,8 +60,13 @@ protected List doInBackground(Void... params) {
// get all oreo shortcuts from system directly
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
List shortcutInfos = ShortcutUtil.getAllShortcuts(context);
for (ShortcutInfo shortcutInfo : shortcutInfos) {
+ LauncherUserInfo info = null;
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ info = launcherApps.getLauncherUserInfo(shortcutInfo.getUserHandle());
+ }
if (isCancelled()) {
break;
}
@@ -62,7 +75,16 @@ protected List doInBackground(Void... params) {
if (shortcutRecord != null) {
boolean disabled = PackageManagerUtils.isAppSuspended(context, shortcutInfo.getPackage(), new UserHandle(context, shortcutInfo.getUserHandle())) || userManager.isQuietModeEnabled(shortcutInfo.getUserHandle());
ShortcutPojo pojo = createPojo(shortcutRecord, tagsHandler, ShortcutUtil.getComponentName(context, shortcutInfo), shortcutInfo.isPinned(), shortcutInfo.isDynamic(), disabled);
- pojos.add(pojo);
+ if ((android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ && (info != null)) {
+ boolean privateSpaceShortcutsDisabled = prefs.getBoolean("disable-private-space-shortcuts", true);
+ if (shouldAddShortcut(userManager, info, shortcutInfo.getUserHandle(),
+ privateSpaceShortcutsDisabled)) {
+ pojos.add(pojo);
+ }
+ } else {
+ pojos.add(pojo);
+ }
}
}
}
@@ -71,10 +93,30 @@ protected List doInBackground(Void... params) {
return pojos;
}
+ @RequiresApi(35)
+ private boolean shouldAddShortcut(UserManager manager, LauncherUserInfo info,
+ android.os.UserHandle profile, boolean privateSpaceShortcutsDisabled) {
+ if (!info.getUserType().equalsIgnoreCase(UserManager.USER_TYPE_PROFILE_PRIVATE)) {
+ return true;
+ } else {
+ if (privateSpaceShortcutsDisabled) {
+ return false;
+ }
+ else return !isQuietModeEnabled(manager, profile);
+ }
+ }
+
private ShortcutPojo createPojo(ShortcutRecord shortcutRecord, TagsHandler tagsHandler, String componentName, boolean pinned, boolean dynamic, boolean disabled) {
ShortcutPojo pojo = new ShortcutPojo(shortcutRecord, componentName, pinned, dynamic, disabled);
pojo.setName(shortcutRecord.name);
pojo.setTags(tagsHandler.getTags(pojo.id));
return pojo;
}
+
+ private boolean isQuietModeEnabled(UserManager manager, android.os.UserHandle profile) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ return manager.isQuietModeEnabled(profile);
+ }
+ return false;
+ }
}
diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml
index 43df5ecd4e..b868486cd2 100644
--- a/app/src/main/res/menu/menu_main.xml
+++ b/app/src/main/res/menu/menu_main.xml
@@ -17,5 +17,9 @@
android:id="@+id/settings"
android:showAsAction="never"
android:title="@string/menu_settings" />
+
diff --git a/app/src/main/res/values-v35/themes.xml b/app/src/main/res/values-v35/themes.xml
new file mode 100644
index 0000000000..e84ae5d507
--- /dev/null
+++ b/app/src/main/res/values-v35/themes.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d8316d8eed..2e45353653 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -356,4 +356,6 @@
Tagged result sort mode
Select how results should be sorted when shown by tag
Tags
+ Private Space
+ Disable Private Space app shortcuts
\ 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 9ff1876266..1f02981044 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -408,6 +408,10 @@
android:defaultValue="false"
android:key="call-contact-on-click"
android:title="@string/contacts_call_on_click" />
+