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