From 97a46d3dab0048ff89a7592200517752004d3092 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Wed, 22 Aug 2018 20:53:55 +0800 Subject: [PATCH] AppListFragment: implement unfreeze & launch shortcut --- app/src/main/AndroidManifest.xml | 2 + .../typeblog/shelter/ui/AppListFragment.java | 71 +++++++++++++++++++ .../typeblog/shelter/ui/DummyActivity.java | 41 ++++++++++- .../net/typeblog/shelter/util/Utility.java | 5 ++ app/src/main/res/values/strings.xml | 3 + 5 files changed, 121 insertions(+), 1 deletion(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c58ccf8..61406df 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -40,6 +40,8 @@ + + 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 2883b22..345190c 100644 --- a/app/src/main/java/net/typeblog/shelter/ui/AppListFragment.java +++ b/app/src/main/java/net/typeblog/shelter/ui/AppListFragment.java @@ -1,11 +1,18 @@ package net.typeblog.shelter.ui; import android.app.Activity; +import android.app.PendingIntent; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +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; @@ -26,10 +33,13 @@ import android.widget.Toast; import net.typeblog.shelter.R; import net.typeblog.shelter.services.IAppInstallCallback; +import net.typeblog.shelter.services.ILoadIconCallback; import net.typeblog.shelter.services.IShelterService; import net.typeblog.shelter.services.ShelterService; import net.typeblog.shelter.util.ApplicationInfoWrapper; +import java.util.Collections; + public class AppListFragment extends Fragment { private static final String BROADCAST_REFRESH = "net.typeblog.shelter.broadcast.REFRESH"; @@ -38,6 +48,8 @@ public class AppListFragment extends Fragment { private static final int MENU_ITEM_UNINSTALL = 10002; private static final int MENU_ITEM_FREEZE = 10003; private static final int MENU_ITEM_UNFREEZE = 10004; + private static final int MENU_ITEM_LAUNCH = 10005; + private static final int MENU_ITEM_CREATE_UNFREEZE_SHORTCUT = 10006; private IShelterService mService = null; private boolean mIsRemote = false; @@ -143,9 +155,12 @@ public class AppListFragment extends Fragment { // Freezing / Unfreezing is only available in profiles that we can control if (mSelectedApp.isHidden()) { menu.add(Menu.NONE, MENU_ITEM_UNFREEZE, Menu.NONE, R.string.unfreeze_app); + menu.add(Menu.NONE, MENU_ITEM_LAUNCH, Menu.NONE, R.string.unfreeze_and_launch); } else { 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); } + menu.add(Menu.NONE, MENU_ITEM_CREATE_UNFREEZE_SHORTCUT, Menu.NONE, R.string.create_unfreeze_shortcut); } else { menu.add(Menu.NONE, MENU_ITEM_CLONE, Menu.NONE, R.string.clone_to_work_profile); } @@ -196,6 +211,30 @@ public class AppListFragment extends Fragment { getString(R.string.unfreeze_success, mSelectedApp.getLabel()), Toast.LENGTH_SHORT).show(); mAdapter.refresh(); return true; + case MENU_ITEM_LAUNCH: + // LAUNCH and UNFREEZE_AND_LAUNCH share the same ID + // because the implementation of UNFREEZE_AND_LAUNCH in DummyActivity + // will work for both + Intent intent = new Intent(DummyActivity.UNFREEZE_AND_LAUNCH); + intent.setComponent(new ComponentName(getContext(), DummyActivity.class)); + intent.putExtra("packageName", mSelectedApp.getPackageName()); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + return true; + case MENU_ITEM_CREATE_UNFREEZE_SHORTCUT: + final ApplicationInfoWrapper app = mSelectedApp; + try { + // Call the service to load the latest icon + mService.loadIcon(app, new ILoadIconCallback.Stub() { + @Override + public void callback(Bitmap icon) { + addUnfreezeShortcut(app, icon); + } + }); + } catch (RemoteException e) { + // Ignore + } + return true; } return super.onContextItemSelected(item); @@ -236,4 +275,36 @@ public class AppListFragment extends Fragment { R.string.uninstall_fail_system_app), Toast.LENGTH_SHORT).show(); } } + + void addUnfreezeShortcut(ApplicationInfoWrapper app, Bitmap icon) { + // First, create an Intent to be sent when clicking on the shortcut + Intent launchIntent = new Intent(DummyActivity.PUBLIC_UNFREEZE_AND_LAUNCH); + launchIntent.setComponent(new ComponentName(getContext(), DummyActivity.class)); + launchIntent.putExtra("packageName", app.getPackageName()); + launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + // Then tell the launcher to add the shortcut + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + ShortcutManager shortcutManager = getContext().getSystemService(ShortcutManager.class); + + if (shortcutManager.isRequestPinShortcutSupported()) { + ShortcutInfo info = new ShortcutInfo.Builder(getContext(), "shelter-" + app.getPackageName()) + .setIntent(launchIntent) + .setIcon(Icon.createWithBitmap(icon)) + .setShortLabel(app.getLabel()) + .setLongLabel(app.getLabel()) + .build(); + Intent addIntent = shortcutManager.createShortcutResultIntent(info); + shortcutManager.requestPinShortcut(info, + PendingIntent.getBroadcast(getContext(), 0, addIntent, 0).getIntentSender()); + } else { + // TODO: Maybe implement this for launchers without pin shortcut support? + // TODO: Should be the same with the fallback for Android < O + throw new RuntimeException("unimplemented"); + } + } else { + // TODO: Maybe backport for Android < O? + throw new RuntimeException("unimplemented"); + } + } } diff --git a/app/src/main/java/net/typeblog/shelter/ui/DummyActivity.java b/app/src/main/java/net/typeblog/shelter/ui/DummyActivity.java index 4b40c7f..dc9a5c9 100644 --- a/app/src/main/java/net/typeblog/shelter/ui/DummyActivity.java +++ b/app/src/main/java/net/typeblog/shelter/ui/DummyActivity.java @@ -16,6 +16,7 @@ import android.widget.Toast; import net.typeblog.shelter.R; import net.typeblog.shelter.ShelterApplication; +import net.typeblog.shelter.receivers.ShelterDeviceAdminReceiver; import net.typeblog.shelter.services.IAppInstallCallback; import net.typeblog.shelter.util.Utility; @@ -32,16 +33,20 @@ public class DummyActivity extends Activity { public static final String TRY_START_SERVICE = "net.typeblog.shelter.action.TRY_START_SERVICE"; public static final String INSTALL_PACKAGE = "net.typeblog.shelter.action.INSTALL_PACKAGE"; public static final String UNINSTALL_PACKAGE = "net.typeblog.shelter.action.UNINSTALL_PACKAGE"; + public static final String UNFREEZE_AND_LAUNCH = "net.typeblog.shelter.action.UNFREEZE_AND_LAUNCH"; + public static final String PUBLIC_UNFREEZE_AND_LAUNCH = "net.typeblog.shelter.action.PUBLIC_UNFREEZE_AND_LAUNCH"; private static final int REQUEST_INSTALL_PACKAGE = 1; private boolean mIsProfileOwner = false; + private DevicePolicyManager mPolicyManager = null; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mIsProfileOwner = getSystemService(DevicePolicyManager.class).isProfileOwnerApp(getPackageName()); + mPolicyManager = getSystemService(DevicePolicyManager.class); + mIsProfileOwner = mPolicyManager.isProfileOwnerApp(getPackageName()); if (mIsProfileOwner) { // If we are the profile owner, we enforce all our policies // so that we can make sure those are updated with our app @@ -63,6 +68,8 @@ public class DummyActivity extends Activity { actionUninstallPackage(); } else if (FINALIZE_PROVISION.equals(intent.getAction())) { actionFinalizeProvision(); + } else if (UNFREEZE_AND_LAUNCH.equals(intent.getAction()) || PUBLIC_UNFREEZE_AND_LAUNCH.equals(intent.getAction())) { + actionUnfreezeAndLaunch(); } else { finish(); } @@ -158,4 +165,36 @@ public class DummyActivity extends Activity { finish(); } + + private void actionUnfreezeAndLaunch() { + // Unfreeze and launch an app + // (actually this also works if the app is not frozen at all) + // For now we only support apps in Work profile, + // so we just check if we are profile owner here + if (!mIsProfileOwner) { + // Forward it to work profile + Intent intent = new Intent(UNFREEZE_AND_LAUNCH); + Utility.transferIntentToProfile(this, intent); + intent.putExtra("packageName", getIntent().getStringExtra("packageName")); + startActivity(intent); + finish(); + return; + } + + String packageName = getIntent().getStringExtra("packageName"); + + // Unfreeze the app first + mPolicyManager.setApplicationHidden( + new ComponentName(this, ShelterDeviceAdminReceiver.class), + packageName, false); + + // Query the start intent + Intent launchIntent = getPackageManager().getLaunchIntentForPackage(packageName); + + if (launchIntent != null) { + startActivity(launchIntent); + } + + finish(); + } } diff --git a/app/src/main/java/net/typeblog/shelter/util/Utility.java b/app/src/main/java/net/typeblog/shelter/util/Utility.java index f257eaa..d8cf790 100644 --- a/app/src/main/java/net/typeblog/shelter/util/Utility.java +++ b/app/src/main/java/net/typeblog/shelter/util/Utility.java @@ -57,6 +57,11 @@ public class Utility { new IntentFilter(DummyActivity.TRY_START_SERVICE), DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT); + manager.addCrossProfileIntentFilter( + adminComponent, + new IntentFilter(DummyActivity.UNFREEZE_AND_LAUNCH), + DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT); + manager.setProfileEnabled(adminComponent); } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b6522a8..da90cdc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -20,6 +20,9 @@ Uninstall Freeze Unfreeze + Launch + Create Unfreeze and/or Launch Shortcut + Unfreeze and Launch Application "%s" cloned successfully Application "%s" uninstalled successfully Application "%s" frozen successfully