256 lines
11 KiB
Java
256 lines
11 KiB
Java
package net.typeblog.shelter.ui;
|
|
|
|
import android.app.Activity;
|
|
import android.app.admin.DevicePolicyManager;
|
|
import android.content.ComponentName;
|
|
import android.content.Intent;
|
|
import android.content.ServiceConnection;
|
|
import android.net.Uri;
|
|
import android.os.Build;
|
|
import android.os.Bundle;
|
|
import android.os.Handler;
|
|
import android.os.IBinder;
|
|
import android.os.RemoteException;
|
|
import android.os.StrictMode;
|
|
import android.support.annotation.Nullable;
|
|
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.LocalStorageManager;
|
|
import net.typeblog.shelter.util.Utility;
|
|
|
|
import java.io.File;
|
|
|
|
// DummyActivity does nothing about presenting any UI
|
|
// It is a wrapper over various different operations
|
|
// that might be required to perform across user profiles
|
|
// which is only possible through Intents that are in
|
|
// the crossProfileIntentFilter
|
|
public class DummyActivity extends Activity {
|
|
public static final String FINALIZE_PROVISION = "net.typeblog.shelter.action.FINALIZE_PROVISION";
|
|
public static final String START_SERVICE = "net.typeblog.shelter.action.START_SERVICE";
|
|
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";
|
|
public static final String PUBLIC_FREEZE_ALL = "net.typeblog.shelter.action.PUBLIC_FREEZE_ALL";
|
|
public static final String FREEZE_ALL_IN_LIST = "net.typeblog.shelter.action.FREEZE_ALL_IN_LIST";
|
|
|
|
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);
|
|
|
|
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
|
|
Utility.enforceWorkProfilePolicies(this);
|
|
Utility.enforceUserRestrictions(this);
|
|
}
|
|
|
|
Intent intent = getIntent();
|
|
if (START_SERVICE.equals(intent.getAction())) {
|
|
actionStartService();
|
|
} else if (TRY_START_SERVICE.equals(intent.getAction())) {
|
|
// Dummy activity with dummy intent won't ever fail :)
|
|
// This is used for testing if work mode is disabled from MainActivity
|
|
setResult(RESULT_OK);
|
|
finish();
|
|
} else if (INSTALL_PACKAGE.equals(intent.getAction())) {
|
|
actionInstallPackage();
|
|
} else if (UNINSTALL_PACKAGE.equals(intent.getAction())) {
|
|
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 if (PUBLIC_FREEZE_ALL.equals(intent.getAction())) {
|
|
actionPublicFreezeAll();
|
|
} else if (FREEZE_ALL_IN_LIST.equals(intent.getAction())) {
|
|
actionFreezeAllInList();
|
|
} else {
|
|
finish();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
super.onActivityResult(requestCode, resultCode, data);
|
|
|
|
if (requestCode == REQUEST_INSTALL_PACKAGE) {
|
|
appInstallFinished(resultCode);
|
|
}
|
|
}
|
|
|
|
private void actionFinalizeProvision() {
|
|
if (mIsProfileOwner) {
|
|
// This is the action used by DeviceAdminReceiver to finalize the setup
|
|
// The work has been finished in onCreate(), now we just have to
|
|
// inform the main profile about this
|
|
Intent intent = new Intent(FINALIZE_PROVISION);
|
|
Utility.transferIntentToProfile(this, intent);
|
|
startActivity(intent);
|
|
finish();
|
|
} else {
|
|
// Set the flag telling MainActivity that we have now finished provisioning
|
|
LocalStorageManager.getInstance()
|
|
.setBoolean(LocalStorageManager.PREF_HAS_SETUP, true);
|
|
LocalStorageManager.getInstance()
|
|
.setBoolean(LocalStorageManager.PREF_IS_SETTING_UP, false);
|
|
Intent intent = new Intent(Intent.ACTION_MAIN);
|
|
intent.setComponent(new ComponentName(this, MainActivity.class));
|
|
startActivity(intent);
|
|
Toast.makeText(this, getString(R.string.provision_finished), Toast.LENGTH_LONG).show();
|
|
finish();
|
|
}
|
|
}
|
|
|
|
private void actionStartService() {
|
|
// This needs to be foreground because this activity won't be able to hold
|
|
// the ServiceConnection to it.
|
|
((ShelterApplication) getApplication()).bindShelterService(new ServiceConnection() {
|
|
@Override
|
|
public void onServiceConnected(ComponentName name, IBinder service) {
|
|
Intent data = new Intent();
|
|
Bundle bundle = new Bundle();
|
|
bundle.putBinder("service", service);
|
|
data.putExtra("extra", bundle);
|
|
setResult(RESULT_OK, data);
|
|
finish();
|
|
}
|
|
|
|
@Override
|
|
public void onServiceDisconnected(ComponentName name) {
|
|
// dummy
|
|
}
|
|
}, true);
|
|
}
|
|
|
|
private void actionInstallPackage() {
|
|
Uri uri = Uri.fromParts("package", getIntent().getStringExtra("package"), null);
|
|
StrictMode.VmPolicy policy = StrictMode.getVmPolicy();
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
// I really have no idea about why the "package:" uri do not work
|
|
// after Android O, anyway we fall back to using the apk path...
|
|
// Since I have plan to support pre-O in later versions, I keep this
|
|
// branch in case that we reduce minSDK in the future.
|
|
uri = Uri.fromFile(new File(getIntent().getStringExtra("apk")));
|
|
|
|
// A permissive VmPolicy must be set to work around
|
|
// the limitation on cross-application Uri
|
|
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().build());
|
|
}
|
|
|
|
Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE, uri);
|
|
intent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, getPackageName());
|
|
intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
|
|
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
|
|
startActivityForResult(intent, REQUEST_INSTALL_PACKAGE);
|
|
|
|
// Restore the VmPolicy anyway
|
|
StrictMode.setVmPolicy(policy);
|
|
}
|
|
|
|
private void actionUninstallPackage() {
|
|
Uri uri = Uri.fromParts("package", getIntent().getStringExtra("package"), null);
|
|
Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, uri);
|
|
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
|
|
// Currently, Install & Uninstall share the same logic
|
|
// after starting the system PackageInstaller
|
|
// because the only thing to do is to call the callback
|
|
// with the result code.
|
|
// If ANY separate logic is added for any of them,
|
|
// the request code should be separated.
|
|
startActivityForResult(intent, REQUEST_INSTALL_PACKAGE);
|
|
}
|
|
|
|
private void appInstallFinished(int resultCode) {
|
|
// Send the result code back to the caller
|
|
Bundle callbackExtra = getIntent().getBundleExtra("callback");
|
|
IAppInstallCallback callback = IAppInstallCallback.Stub
|
|
.asInterface(callbackExtra.getBinder("callback"));
|
|
|
|
try {
|
|
callback.callback(resultCode);
|
|
} catch (RemoteException e) {
|
|
// do nothing
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
private void actionPublicFreezeAll() {
|
|
// For now we only support freezing apps in work profile
|
|
// so forward this to DummyActivity in work profile
|
|
// after loading the full list to freeze
|
|
if (!mIsProfileOwner) {
|
|
Intent intent = new Intent(FREEZE_ALL_IN_LIST);
|
|
Utility.transferIntentToProfile(this, intent);
|
|
String[] list = LocalStorageManager.getInstance()
|
|
.getStringList(LocalStorageManager.PREF_AUTO_FREEZE_LIST_WORK_PROFILE);
|
|
intent.putExtra("list", list);
|
|
startActivity(intent);
|
|
finish();
|
|
} else {
|
|
throw new RuntimeException("unimplemented");
|
|
}
|
|
}
|
|
|
|
private void actionFreezeAllInList() {
|
|
if (mIsProfileOwner) {
|
|
String[] list = getIntent().getStringArrayExtra("list");
|
|
for (String pkg : list) {
|
|
mPolicyManager.setApplicationHidden(
|
|
new ComponentName(this, ShelterDeviceAdminReceiver.class),
|
|
pkg, true);
|
|
}
|
|
Toast.makeText(this, R.string.freeze_all_success, Toast.LENGTH_SHORT).show();
|
|
finish();
|
|
} else {
|
|
finish();
|
|
}
|
|
}
|
|
}
|