diff --git a/app/build.gradle b/app/build.gradle index 8d02597..35ce379 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,6 +9,34 @@ repositories { } } +def getVersionCode = { -> + try { + def stdout = new ByteArrayOutputStream() + exec { + commandLine 'git', 'rev-list', '--first-parent', '--count', 'master' + standardOutput = stdout + } + return Integer.parseInt(stdout.toString().trim()) + } + catch (ignored) { + return -1; + } +} + +def getVersionName = { -> + try { + def stdout = new ByteArrayOutputStream() + exec { + commandLine 'git', 'describe', '--tags', '--dirty' + standardOutput = stdout + } + return stdout.toString().trim() + } + catch (ignored) { + return null; + } +} + android { compileSdk 34 buildToolsVersion = '34.0.0' @@ -16,8 +44,8 @@ android { applicationId "net.typeblog.shelter" minSdkVersion 24 targetSdkVersion 34 - versionCode 24 - versionName "1.9-dev" + versionCode getVersionCode() + versionName getVersionName() testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/app/src/main/aidl/net/typeblog/shelter/services/IShelterService.aidl b/app/src/main/aidl/net/typeblog/shelter/services/IShelterService.aidl index 7f3bc01..dd71266 100644 --- a/app/src/main/aidl/net/typeblog/shelter/services/IShelterService.aidl +++ b/app/src/main/aidl/net/typeblog/shelter/services/IShelterService.aidl @@ -26,4 +26,6 @@ interface IShelterService { List getCrossProfileWidgetProviders(); boolean setCrossProfileWidgetProviderEnabled(String pkgName, boolean enabled); void setStartActivityProxy(in IStartActivityProxy proxy); + List getCrossProfilePackages(); + void setCrossProfilePackages(in List packages); } diff --git a/app/src/main/java/net/typeblog/shelter/services/ShelterService.java b/app/src/main/java/net/typeblog/shelter/services/ShelterService.java index d9fa83b..c1fe470 100644 --- a/app/src/main/java/net/typeblog/shelter/services/ShelterService.java +++ b/app/src/main/java/net/typeblog/shelter/services/ShelterService.java @@ -25,6 +25,8 @@ import net.typeblog.shelter.util.FileProviderProxy; import net.typeblog.shelter.util.UriForwardProxy; import net.typeblog.shelter.util.Utility; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.stream.Collectors; @@ -272,6 +274,24 @@ public class ShelterService extends Service { public void setStartActivityProxy(IStartActivityProxy proxy) { mStartActivityProxy = proxy; } + + @Override + public List getCrossProfilePackages() throws RemoteException { + if (!mIsProfileOwner) + throw new IllegalStateException("Cannot access cross-profile packages without being profile owner"); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) + throw new IllegalStateException("Cross-profile packages support is only available on Android 11 and later"); + return new ArrayList<>(mPolicyManager.getCrossProfilePackages(mAdminComponent)); + } + + @Override + public void setCrossProfilePackages(List packages) throws RemoteException { + if (!mIsProfileOwner) + throw new IllegalStateException("Cannot access cross-profile packages without being profile owner"); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) + throw new IllegalStateException("Cross-profile packages support is only available on Android 11 and later"); + mPolicyManager.setCrossProfilePackages(mAdminComponent, new HashSet<>(packages)); + } }; @Override diff --git a/app/src/main/java/net/typeblog/shelter/ui/AppListFragment.java b/app/src/main/java/net/typeblog/shelter/ui/AppListFragment.java index 84d4155..07ce4dd 100644 --- a/app/src/main/java/net/typeblog/shelter/ui/AppListFragment.java +++ b/app/src/main/java/net/typeblog/shelter/ui/AppListFragment.java @@ -9,6 +9,7 @@ import android.content.IntentFilter; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; @@ -40,6 +41,7 @@ import net.typeblog.shelter.util.ApplicationInfoWrapper; import net.typeblog.shelter.util.LocalStorageManager; import net.typeblog.shelter.util.Utility; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -57,6 +59,7 @@ public class AppListFragment extends BaseFragment { private static final int MENU_ITEM_CREATE_UNFREEZE_SHORTCUT = 10006; private static final int MENU_ITEM_AUTO_FREEZE = 10007; private static final int MENU_ITEM_ALLOW_CROSS_PROFILE_WIDGET = 10008; + private static final int MENU_ITEM_ALLOW_CROSS_PROFILE_INTERACTION = 10009; private IShelterService mService = null; private boolean mIsRemote = false; @@ -68,6 +71,9 @@ public class AppListFragment extends BaseFragment { // Only useful if this fragment manages the work profile private Set mCrossProfileWidgetProviders = new HashSet<>(); + // Packages allowed to interact across profiles + private Set mCrossProfilePackages = new HashSet<>(); + // Views private RecyclerView mList = null; private AppListAdapter mAdapter = null; @@ -200,11 +206,13 @@ public class AppListFragment extends BaseFragment { public void callback(List apps) { if (mIsRemote) { mCrossProfileWidgetProviders.clear(); + mCrossProfilePackages.clear(); - // Update the cross-profile widget provider list + // Update the cross-profile packages / widget providers list try { mCrossProfileWidgetProviders.addAll(mService.getCrossProfileWidgetProviders()); - } catch (RemoteException e) { + mCrossProfilePackages.addAll(mService.getCrossProfilePackages()); + } catch (RemoteException ignored) { } } @@ -288,7 +296,7 @@ public class AppListFragment extends BaseFragment { menu.add(Menu.NONE, MENU_ITEM_FREEZE, Menu.NONE, R.string.freeze_app); menu.add(Menu.NONE, MENU_ITEM_LAUNCH, Menu.NONE, R.string.launch); } - // Cross-profile widget settings is also limited to work profile + // Cross-profile widget / packages settings is also limited to work profile MenuItem crossProfileWdiegt = menu.add(Menu.NONE, MENU_ITEM_ALLOW_CROSS_PROFILE_WIDGET, Menu.NONE, R.string.allow_cross_profile_widgets); @@ -296,6 +304,15 @@ public class AppListFragment extends BaseFragment { crossProfileWdiegt.setChecked( mCrossProfileWidgetProviders.contains(mSelectedApp.getPackageName())); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + MenuItem crossProfileInteraction = + menu.add(Menu.NONE, MENU_ITEM_ALLOW_CROSS_PROFILE_INTERACTION, Menu.NONE, + R.string.allow_cross_profile_interaction); + crossProfileInteraction.setCheckable(true); + crossProfileInteraction.setChecked( + mCrossProfilePackages.contains(mSelectedApp.getPackageName())); + } + // TODO: If we implement God Mode (i.e. Shelter as device owner), we should // TODO: use two different lists to store auto freeze apps because we'll be // TODO: able to freeze apps in main profile. @@ -393,7 +410,7 @@ public class AppListFragment extends BaseFragment { LocalStorageManager.PREF_AUTO_FREEZE_LIST_WORK_PROFILE, mSelectedApp.getPackageName()); } return true; - case MENU_ITEM_ALLOW_CROSS_PROFILE_WIDGET: + case MENU_ITEM_ALLOW_CROSS_PROFILE_WIDGET: { boolean newState = !item.isChecked(); try { if (mService.setCrossProfileWidgetProviderEnabled(mSelectedApp.getPackageName(), newState)) { @@ -410,6 +427,22 @@ public class AppListFragment extends BaseFragment { } return true; + } + case MENU_ITEM_ALLOW_CROSS_PROFILE_INTERACTION: { + boolean newState = !item.isChecked(); + if (newState) { + mCrossProfilePackages.add(mSelectedApp.getPackageName()); + } else { + mCrossProfilePackages.remove(mSelectedApp.getPackageName()); + } + try { + mService.setCrossProfilePackages(new ArrayList<>(mCrossProfilePackages)); + item.setChecked(newState); + } catch (RemoteException ignored) { + + } + return true; + } } return super.onContextItemSelected(item); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a749c88..f8af5d9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -51,6 +51,7 @@ Unfreeze and Launch Auto Freeze Allow Widgets in Main Profile + Allow Cross-Profile Interaction Search