implement cross-profile DocumentsProvider
now we can freely access files across profile through Documents UI
This commit is contained in:
parent
08fa244e76
commit
58fc86f93e
|
@ -11,6 +11,7 @@
|
|||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:maxSdkVersion="25"
|
||||
android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:name=".ShelterApplication"
|
||||
|
@ -49,6 +50,10 @@
|
|||
<action android:name="net.typeblog.shelter.action.PUBLIC_UNFREEZE_AND_LAUNCH" />
|
||||
<action android:name="net.typeblog.shelter.action.PUBLIC_FREEZE_ALL" />
|
||||
<action android:name="net.typeblog.shelter.action.FREEZE_ALL_IN_LIST" />
|
||||
<!-- We need two of these to avoid being prompted with an action chooser dialog -->
|
||||
<!-- When the intent is actually already forwarded to work profile -->
|
||||
<action android:name="net.typeblog.shelter.action.START_FILE_SHUTTLE" />
|
||||
<action android:name="net.typeblog.shelter.action.START_FILE_SHUTTLE_2" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
@ -78,12 +83,29 @@
|
|||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
<!-- A DocumentsProvider that lists files in another profile -->
|
||||
<provider
|
||||
android:name="net.typeblog.shelter.util.CrossProfileDocumentsProvider"
|
||||
android:authorities="net.typeblog.shelter.documents"
|
||||
android:grantUriPermissions="true"
|
||||
android:permission="android.permission.MANAGE_DOCUMENTS"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
|
||||
</intent-filter>
|
||||
</provider>
|
||||
|
||||
<!-- Core service running on both the main profile and the work profile -->
|
||||
<!-- Actions like cloning / freezing apps will be performed by this service -->
|
||||
<service android:name=".services.ShelterService"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_DEVICE_ADMIN"/>
|
||||
|
||||
<!-- Service to forward file information between profiles -->
|
||||
<service android:name=".services.FileShuttleService"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_DEVICE_ADMIN" />
|
||||
|
||||
<!-- A hack service to ensure every ShelterService is killed when App is removed -->
|
||||
<!-- from recent tasks -->
|
||||
<service android:name=".services.KillerService" />
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
// IFileShuttleService.aidl
|
||||
package net.typeblog.shelter.services;
|
||||
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
interface IFileShuttleService {
|
||||
void ping();
|
||||
List loadFiles(String path);
|
||||
Map loadFileMeta(String path);
|
||||
ParcelFileDescriptor openFile(String path, String mode);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
// IFileShuttleServiceCallback.aidl
|
||||
package net.typeblog.shelter.services;
|
||||
|
||||
import net.typeblog.shelter.services.IFileShuttleService;
|
||||
|
||||
interface IFileShuttleServiceCallback {
|
||||
void callback(in IFileShuttleService service);
|
||||
}
|
|
@ -5,11 +5,13 @@ import android.content.Context;
|
|||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
|
||||
import net.typeblog.shelter.services.FileShuttleService;
|
||||
import net.typeblog.shelter.services.ShelterService;
|
||||
import net.typeblog.shelter.util.LocalStorageManager;
|
||||
|
||||
public class ShelterApplication extends Application {
|
||||
private ServiceConnection mShelterServiceConnection = null;
|
||||
private ServiceConnection mFileShuttleServiceConnection = null;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
|
@ -25,6 +27,13 @@ public class ShelterApplication extends Application {
|
|||
mShelterServiceConnection = conn;
|
||||
}
|
||||
|
||||
public void bindFileShuttleService(ServiceConnection conn) {
|
||||
unbindFileShuttleService();;
|
||||
Intent intent = new Intent(getApplicationContext(), FileShuttleService.class);
|
||||
bindService(intent, conn, Context.BIND_AUTO_CREATE);
|
||||
mFileShuttleServiceConnection = conn;
|
||||
}
|
||||
|
||||
public void unbindShelterService() {
|
||||
if (mShelterServiceConnection != null) {
|
||||
try {
|
||||
|
@ -38,4 +47,16 @@ public class ShelterApplication extends Application {
|
|||
|
||||
mShelterServiceConnection = null;
|
||||
}
|
||||
|
||||
public void unbindFileShuttleService() {
|
||||
if (mFileShuttleServiceConnection != null) {
|
||||
try {
|
||||
unbindService(mFileShuttleServiceConnection);
|
||||
} catch (Exception e) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
mFileShuttleServiceConnection = null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
package net.typeblog.shelter.services;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import net.typeblog.shelter.ShelterApplication;
|
||||
import net.typeblog.shelter.util.CrossProfileDocumentsProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
// A service to forward file information across the profile boundary
|
||||
public class FileShuttleService extends Service {
|
||||
public static final long TIMEOUT = 10000;
|
||||
// Periodic task to stop the service when idle.
|
||||
// This service does not need to persist.
|
||||
private Runnable mSuicideTask = this::suicide;
|
||||
private Handler mHandler = new Handler(Looper.getMainLooper());
|
||||
private IFileShuttleService.Stub mStub = new IFileShuttleService.Stub() {
|
||||
@Override
|
||||
public void ping() {
|
||||
// Dummy method
|
||||
resetSuicideTask();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map> loadFiles(String path) {
|
||||
resetSuicideTask();
|
||||
ArrayList<Map> ret = new ArrayList<>();
|
||||
File f = new File(resolvePath(path));
|
||||
if (f.listFiles() != null) {
|
||||
for (File child : f.listFiles()) {
|
||||
ret.add(loadFileMeta(child.getPath()));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map loadFileMeta(String path) {
|
||||
resetSuicideTask();
|
||||
File f = new File(resolvePath(path));
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
map.put(DocumentsContract.Document.COLUMN_DOCUMENT_ID, f.getAbsolutePath());
|
||||
map.put(DocumentsContract.Document.COLUMN_DISPLAY_NAME, f.getName());
|
||||
map.put(DocumentsContract.Document.COLUMN_SIZE, f.length());
|
||||
map.put(DocumentsContract.Document.COLUMN_LAST_MODIFIED, f.lastModified());
|
||||
map.put(DocumentsContract.Document.COLUMN_FLAGS, 0);
|
||||
|
||||
if (f.isDirectory()) {
|
||||
map.put(DocumentsContract.Document.COLUMN_MIME_TYPE, DocumentsContract.Document.MIME_TYPE_DIR);
|
||||
} else {
|
||||
String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
|
||||
MimeTypeMap.getFileExtensionFromUrl("file://" + f.getAbsolutePath()));
|
||||
map.put(DocumentsContract.Document.COLUMN_MIME_TYPE, mime);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(String path, String mode) {
|
||||
resetSuicideTask();
|
||||
File f = new File(resolvePath(path));
|
||||
|
||||
try {
|
||||
return ParcelFileDescriptor.open(f, ParcelFileDescriptor.parseMode(mode));
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
resetSuicideTask();
|
||||
return mStub;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
android.util.Log.d("FileShuttleService", "being destroyed");
|
||||
}
|
||||
|
||||
private String resolvePath(String path) {
|
||||
if (path.startsWith(CrossProfileDocumentsProvider.DUMMY_ROOT)) {
|
||||
return path.replaceFirst(CrossProfileDocumentsProvider.DUMMY_ROOT,
|
||||
Environment.getExternalStorageDirectory().getAbsolutePath());
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
private void resetSuicideTask() {
|
||||
mHandler.removeCallbacks(mSuicideTask);
|
||||
mHandler.postDelayed(mSuicideTask, TIMEOUT);
|
||||
}
|
||||
|
||||
private void suicide() {
|
||||
mHandler.removeCallbacks(mSuicideTask);
|
||||
((ShelterApplication) getApplication()).unbindFileShuttleService();
|
||||
stopSelf();
|
||||
}
|
||||
}
|
|
@ -1,23 +1,29 @@
|
|||
package net.typeblog.shelter.ui;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.StrictMode;
|
||||
import android.support.annotation.NonNull;
|
||||
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.FileShuttleService;
|
||||
import net.typeblog.shelter.services.IAppInstallCallback;
|
||||
import net.typeblog.shelter.services.IFileShuttleService;
|
||||
import net.typeblog.shelter.services.IFileShuttleServiceCallback;
|
||||
import net.typeblog.shelter.util.FileProviderProxy;
|
||||
import net.typeblog.shelter.util.LocalStorageManager;
|
||||
import net.typeblog.shelter.util.Utility;
|
||||
|
@ -39,8 +45,15 @@ public class DummyActivity extends Activity {
|
|||
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";
|
||||
// If we use the same intent for parent -> profile and profile -> parent, the user will
|
||||
// be prompted with the action chooser with only one choice in it when the intent is
|
||||
// forwarded by Utility.transferIntentToProfile()
|
||||
// This is a bad experience, so we use two to avoid this.
|
||||
public static final String START_FILE_SHUTTLE = "net.typeblog.shelter.action.START_FILE_SHUTTLE";
|
||||
public static final String START_FILE_SHUTTLE_2 = "net.typeblog.shelter.action.START_FILE_SHUTTLE_2";
|
||||
|
||||
private static final int REQUEST_INSTALL_PACKAGE = 1;
|
||||
private static final int REQUEST_PERMISSION_EXTERNAL_STORAGE= 2;
|
||||
|
||||
private boolean mIsProfileOwner = false;
|
||||
private DevicePolicyManager mPolicyManager = null;
|
||||
|
@ -78,6 +91,8 @@ public class DummyActivity extends Activity {
|
|||
actionPublicFreezeAll();
|
||||
} else if (FREEZE_ALL_IN_LIST.equals(intent.getAction())) {
|
||||
actionFreezeAllInList();
|
||||
} else if (START_FILE_SHUTTLE.equals(intent.getAction()) || START_FILE_SHUTTLE_2.equals(intent.getAction())) {
|
||||
actionStartFileShuttle();
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
|
@ -92,6 +107,19 @@ public class DummyActivity extends Activity {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
if (requestCode == REQUEST_PERMISSION_EXTERNAL_STORAGE) {
|
||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
doStartFileShuttle();
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
} else {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
}
|
||||
|
||||
private void actionFinalizeProvision() {
|
||||
if (mIsProfileOwner) {
|
||||
// This is the action used by DeviceAdminReceiver to finalize the setup
|
||||
|
@ -271,4 +299,36 @@ public class DummyActivity extends Activity {
|
|||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void actionStartFileShuttle() {
|
||||
// This requires the permission WRITE_EXTERNAL_STORAGE
|
||||
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
|
||||
doStartFileShuttle();
|
||||
} else {
|
||||
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_PERMISSION_EXTERNAL_STORAGE);
|
||||
}
|
||||
}
|
||||
|
||||
private void doStartFileShuttle() {
|
||||
((ShelterApplication) getApplication()).bindFileShuttleService(new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
IFileShuttleService shuttle = IFileShuttleService.Stub.asInterface(service);
|
||||
IFileShuttleServiceCallback callback = IFileShuttleServiceCallback.Stub.asInterface(
|
||||
getIntent().getBundleExtra("extra").getBinder("callback"));
|
||||
try {
|
||||
callback.callback(shuttle);
|
||||
} catch (RemoteException e) {
|
||||
// Do Nothing
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
// Do Nothing
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
package net.typeblog.shelter.util;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.os.Bundle;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.DocumentsProvider;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import net.typeblog.shelter.R;
|
||||
import net.typeblog.shelter.services.FileShuttleService;
|
||||
import net.typeblog.shelter.services.IFileShuttleService;
|
||||
import net.typeblog.shelter.services.IFileShuttleServiceCallback;
|
||||
import net.typeblog.shelter.ui.DummyActivity;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
// A document provider to show files across the profile boundary
|
||||
// in the system's Documents UI.
|
||||
// This is an interface to FileShuttleService
|
||||
public class CrossProfileDocumentsProvider extends DocumentsProvider {
|
||||
// The dummy root path that will be replaced by the real path to external storage on the other side
|
||||
public static final String DUMMY_ROOT = "/shelter_storage_root/";
|
||||
private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
|
||||
DocumentsContract.Root.COLUMN_ROOT_ID,
|
||||
DocumentsContract.Root.COLUMN_DOCUMENT_ID, DocumentsContract.Root.COLUMN_ICON,
|
||||
DocumentsContract.Root.COLUMN_TITLE, DocumentsContract.Root.COLUMN_FLAGS
|
||||
};
|
||||
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
|
||||
DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_DISPLAY_NAME,
|
||||
DocumentsContract.Document.COLUMN_FLAGS, DocumentsContract.Document.COLUMN_MIME_TYPE,
|
||||
DocumentsContract.Document.COLUMN_SIZE, DocumentsContract.Document.COLUMN_LAST_MODIFIED
|
||||
};
|
||||
|
||||
private IFileShuttleService mService = null;
|
||||
private Handler mHandler = new Handler(Looper.getMainLooper());
|
||||
// Periodic task to release the handle to the service
|
||||
// Since DocumentsProvider may persist for a long time,
|
||||
// We just release the service when idle, thus enabling the
|
||||
// system to release memory
|
||||
private Runnable mReleaseServiceTask = this::releaseService;
|
||||
private Object mLock = new Object();
|
||||
|
||||
private void doBindService() {
|
||||
// Call DummyActivity on the other side to bind the service for us
|
||||
Intent intent = new Intent(DummyActivity.START_FILE_SHUTTLE);
|
||||
Bundle extra = new Bundle();
|
||||
extra.putBinder("callback", new IFileShuttleServiceCallback.Stub() {
|
||||
@Override
|
||||
public void callback(IFileShuttleService service) {
|
||||
mService = service;
|
||||
synchronized (mLock) {
|
||||
mLock.notifyAll();
|
||||
}
|
||||
}
|
||||
});
|
||||
intent.putExtra("extra", extra);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
try {
|
||||
Utility.transferIntentToProfile(getContext(), intent);
|
||||
} catch (IllegalStateException e) {
|
||||
// Try with the other action.
|
||||
// We use distinct intent for parent -> profile and profile -> parent,
|
||||
// to avoid the action chooser dialog
|
||||
// so as a dirty hack here, we just try the other if one is not found.
|
||||
intent.setAction(DummyActivity.START_FILE_SHUTTLE_2);
|
||||
Utility.transferIntentToProfile(getContext(), intent);
|
||||
}
|
||||
getContext().startActivity(intent);
|
||||
|
||||
// A hack to convert the asynchronous process of starting service to synchronous
|
||||
synchronized (mLock) {
|
||||
try {
|
||||
mLock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
// ???
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureServiceBound() {
|
||||
if (mService == null) {
|
||||
doBindService();
|
||||
} else {
|
||||
try {
|
||||
mService.ping();
|
||||
resetReleaseService();
|
||||
} catch (RemoteException e) {
|
||||
doBindService();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void releaseService() {
|
||||
mService = null;
|
||||
}
|
||||
|
||||
private void resetReleaseService() {
|
||||
mHandler.removeCallbacks(mReleaseServiceTask);
|
||||
mHandler.postDelayed(mReleaseServiceTask, FileShuttleService.TIMEOUT / 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryRoots(String[] projection) {
|
||||
final MatrixCursor result = new MatrixCursor(DEFAULT_ROOT_PROJECTION);
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
row.add(DocumentsContract.Root.COLUMN_ROOT_ID, DUMMY_ROOT);
|
||||
row.add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, DUMMY_ROOT);
|
||||
row.add(DocumentsContract.Root.COLUMN_ICON, R.mipmap.ic_launcher_egg);
|
||||
row.add(DocumentsContract.Root.COLUMN_TITLE, getContext().getString(R.string.app_name));
|
||||
row.add(DocumentsContract.Root.COLUMN_FLAGS, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryDocument(String documentId, String[] projection) {
|
||||
ensureServiceBound();
|
||||
final MatrixCursor result = new MatrixCursor(DEFAULT_DOCUMENT_PROJECTION);
|
||||
Map<String, Object> fileInfo = null;
|
||||
try {
|
||||
fileInfo = mService.loadFileMeta(documentId);
|
||||
} catch (RemoteException e) {
|
||||
return null;
|
||||
}
|
||||
includeFile(result, fileInfo);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) {
|
||||
ensureServiceBound();
|
||||
List<Map<String, Object>> files = null;
|
||||
try {
|
||||
files = mService.loadFiles(parentDocumentId);
|
||||
} catch (RemoteException e) {
|
||||
return null;
|
||||
}
|
||||
final MatrixCursor result = new MatrixCursor(DEFAULT_DOCUMENT_PROJECTION);
|
||||
|
||||
for (Map<String, Object> file : files) {
|
||||
includeFile(result, file);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openDocument(String documentId, String mode, @Nullable CancellationSignal signal) {
|
||||
ensureServiceBound();
|
||||
try {
|
||||
return mService.openFile(documentId, mode);
|
||||
} catch (RemoteException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void includeFile(MatrixCursor cursor, Map<String, Object> fileInfo) {
|
||||
final MatrixCursor.RowBuilder row = cursor.newRow();
|
||||
for (String col : DEFAULT_DOCUMENT_PROJECTION) {
|
||||
row.add(col, fileInfo.get(col));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -106,6 +106,16 @@ public class Utility {
|
|||
new IntentFilter(DummyActivity.FINALIZE_PROVISION),
|
||||
DevicePolicyManager.FLAG_PARENT_CAN_ACCESS_MANAGED);
|
||||
|
||||
manager.addCrossProfileIntentFilter(
|
||||
adminComponent,
|
||||
new IntentFilter(DummyActivity.START_FILE_SHUTTLE),
|
||||
DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT);
|
||||
|
||||
manager.addCrossProfileIntentFilter(
|
||||
adminComponent,
|
||||
new IntentFilter(DummyActivity.START_FILE_SHUTTLE_2),
|
||||
DevicePolicyManager.FLAG_PARENT_CAN_ACCESS_MANAGED);
|
||||
|
||||
// Allow ACTION_SEND and ACTION_SEND_MULTIPLE to cross from managed to parent
|
||||
// TODO: Make this configurable
|
||||
IntentFilter actionSendFilter = new IntentFilter();
|
||||
|
|
Loading…
Reference in a new issue