Browse Source

Shelter: implement cross-profile interaction

Peter Cai 1 year ago
parent
commit
83ea35cfd0
Signed by: Peter Cai <[email protected]> GPG Key ID: 71F5FB4E4F3FD54F

+ 1
- 1
.idea/misc.xml View File

@@ -25,7 +25,7 @@
25 25
       </value>
26 26
     </option>
27 27
   </component>
28
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
28
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
29 29
     <output url="file://$PROJECT_DIR$/build/classes" />
30 30
   </component>
31 31
   <component name="ProjectType">

+ 6
- 2
app/build.gradle View File

@@ -4,7 +4,7 @@ android {
4 4
     compileSdkVersion 28
5 5
     defaultConfig {
6 6
         applicationId "net.typeblog.shelter"
7
-        minSdkVersion 24
7
+        minSdkVersion 26
8 8
         targetSdkVersion 28
9 9
         versionCode 1
10 10
         versionName "1.0"
@@ -16,10 +16,14 @@ android {
16 16
             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 17
         }
18 18
     }
19
+    compileOptions {
20
+        sourceCompatibility JavaVersion.VERSION_1_8
21
+        targetCompatibility JavaVersion.VERSION_1_8
22
+    }
19 23
 }
20 24
 
21 25
 dependencies {
22
-    implementation fileTree(dir: 'libs', include: ['*.jar'])
26
+    implementation fileTree(include: ['*.jar'], dir: 'libs')
23 27
     implementation 'com.android.support:appcompat-v7:28.0.0-rc01'
24 28
     implementation 'com.android.support.constraint:constraint-layout:1.1.2'
25 29
     testImplementation 'junit:junit:4.12'

+ 14
- 0
app/src/main/AndroidManifest.xml View File

@@ -2,6 +2,10 @@
2 2
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3 3
     package="net.typeblog.shelter">
4 4
 
5
+    <uses-feature android:name="android.software.device_admin" android:required="true"/>
6
+    <uses-feature android:name="android.software.managed_users" android:required="true"/>
7
+    <!--<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>-->
8
+
5 9
     <application
6 10
         android:name=".ShelterApplication"
7 11
         android:allowBackup="false"
@@ -17,6 +21,13 @@
17 21
                 <category android:name="android.intent.category.LAUNCHER" />
18 22
             </intent-filter>
19 23
         </activity>
24
+        <activity android:name=".ui.DummyActivity"
25
+            android:theme="@android:style/Theme.Translucent.NoTitleBar">
26
+            <intent-filter>
27
+                <action android:name="net.typeblog.shelter.action.START_SERVICE"/>
28
+                <category android:name="android.intent.category.DEFAULT" />
29
+            </intent-filter>
30
+        </activity>
20 31
         <receiver android:name=".receivers.ShelterDeviceAdminReceiver"
21 32
             android:label="@string/device_admin_label"
22 33
             android:description="@string/device_admin_desc"
@@ -27,6 +38,9 @@
27 38
                 <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
28 39
             </intent-filter>
29 40
         </receiver>
41
+        <service android:name=".services.ShelterService"
42
+                android:exported="true"
43
+                android:permission="android.permission.BIND_DEVICE_ADMIN"/>
30 44
     </application>
31 45
 
32 46
 </manifest>

+ 9
- 0
app/src/main/aidl/net/typeblog/shelter/services/IShelterService.aidl View File

@@ -0,0 +1,9 @@
1
+// IShelterService.aidl
2
+package net.typeblog.shelter.services;
3
+
4
+import android.content.pm.ResolveInfo;
5
+
6
+interface IShelterService {
7
+    void stopShelterService(boolean kill);
8
+    List<ResolveInfo> getApps();
9
+}

+ 21
- 0
app/src/main/java/net/typeblog/shelter/ShelterApplication.java View File

@@ -1,13 +1,34 @@
1 1
 package net.typeblog.shelter;
2 2
 
3 3
 import android.app.Application;
4
+import android.content.Context;
5
+import android.content.Intent;
6
+import android.content.ServiceConnection;
4 7
 
8
+import net.typeblog.shelter.services.ShelterService;
5 9
 import net.typeblog.shelter.util.LocalStorageManager;
6 10
 
7 11
 public class ShelterApplication extends Application {
12
+    private ServiceConnection mShelterServiceConnection = null;
13
+
8 14
     @Override
9 15
     public void onCreate() {
10 16
         super.onCreate();
11 17
         LocalStorageManager.initialize(this);
12 18
     }
19
+
20
+    public void bindShelterService(ServiceConnection conn) {
21
+        unbindShelterService();
22
+        Intent intent = new Intent(getApplicationContext(), ShelterService.class);
23
+        bindService(intent, conn, Context.BIND_AUTO_CREATE);
24
+        mShelterServiceConnection = conn;
25
+    }
26
+
27
+    public void unbindShelterService() {
28
+        if (mShelterServiceConnection != null) {
29
+            unbindService(mShelterServiceConnection);
30
+        }
31
+
32
+        mShelterServiceConnection = null;
33
+    }
13 34
 }

+ 10
- 2
app/src/main/java/net/typeblog/shelter/receivers/ShelterDeviceAdminReceiver.java View File

@@ -5,6 +5,7 @@ import android.app.admin.DevicePolicyManager;
5 5
 import android.content.ComponentName;
6 6
 import android.content.Context;
7 7
 import android.content.Intent;
8
+import android.content.IntentFilter;
8 9
 import android.content.pm.PackageManager;
9 10
 
10 11
 import net.typeblog.shelter.ui.MainActivity;
@@ -26,14 +27,21 @@ public class ShelterDeviceAdminReceiver extends DeviceAdminReceiver {
26 27
     @Override
27 28
     public void onProfileProvisioningComplete(Context context, Intent intent) {
28 29
         super.onProfileProvisioningComplete(context, intent);
30
+        DevicePolicyManager manager = context.getSystemService(DevicePolicyManager.class);
31
+        ComponentName adminComponent = new ComponentName(context.getApplicationContext(), ShelterDeviceAdminReceiver.class);
29 32
 
30 33
         // Enable the profile
31
-        DevicePolicyManager manager = context.getSystemService(DevicePolicyManager.class);
32
-        manager.setProfileEnabled(new ComponentName(context.getApplicationContext(), ShelterDeviceAdminReceiver.class));
34
+        manager.setProfileEnabled(adminComponent);
33 35
 
34 36
         // Hide this app in the work profile
35 37
         context.getPackageManager().setComponentEnabledSetting(
36 38
                 new ComponentName(context.getApplicationContext(), MainActivity.class),
37 39
                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0);
40
+
41
+        // Allow cross-profile intents for START_SERVICE
42
+        manager.addCrossProfileIntentFilter(
43
+                adminComponent,
44
+                new IntentFilter("net.typeblog.shelter.action.START_SERVICE"),
45
+                DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT);
38 46
     }
39 47
 }

+ 56
- 0
app/src/main/java/net/typeblog/shelter/services/ShelterService.java View File

@@ -0,0 +1,56 @@
1
+package net.typeblog.shelter.services;
2
+
3
+import android.app.Service;
4
+import android.app.admin.DevicePolicyManager;
5
+import android.content.Intent;
6
+import android.content.pm.ResolveInfo;
7
+import android.os.IBinder;
8
+import android.support.annotation.Nullable;
9
+
10
+import net.typeblog.shelter.ShelterApplication;
11
+
12
+import java.util.List;
13
+
14
+public class ShelterService extends Service {
15
+    private DevicePolicyManager mPolicyManager = null;
16
+    private boolean mIsWorkProfile = false;
17
+    private IShelterService.Stub mBinder = new IShelterService.Stub() {
18
+        @Override
19
+        public void stopShelterService(boolean kill) {
20
+            // dirty: just wait for some time and kill this service itself
21
+            new Thread(() -> {
22
+                try {
23
+                    Thread.sleep(1);
24
+                } catch (Exception e) {
25
+
26
+                }
27
+
28
+                ((ShelterApplication) getApplication()).unbindShelterService();
29
+
30
+                if (kill) {
31
+                    // Just kill the entire process if this signal is received
32
+                    System.exit(0);
33
+                }
34
+            }).start();
35
+        }
36
+
37
+        @Override
38
+        public List<ResolveInfo> getApps() {
39
+            Intent mainIntent = new Intent(Intent.ACTION_MAIN);
40
+            mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
41
+            return getPackageManager().queryIntentActivities(mainIntent, 0);
42
+        }
43
+    };
44
+
45
+    @Override
46
+    public void onCreate() {
47
+        mPolicyManager = getSystemService(DevicePolicyManager.class);
48
+        mIsWorkProfile = mPolicyManager.isProfileOwnerApp(getPackageName());
49
+    }
50
+
51
+    @Nullable
52
+    @Override
53
+    public IBinder onBind(Intent intent) {
54
+        return mBinder;
55
+    }
56
+}

+ 35
- 0
app/src/main/java/net/typeblog/shelter/ui/DummyActivity.java View File

@@ -0,0 +1,35 @@
1
+package net.typeblog.shelter.ui;
2
+
3
+import android.app.Activity;
4
+import android.content.ComponentName;
5
+import android.content.Context;
6
+import android.content.Intent;
7
+import android.content.ServiceConnection;
8
+import android.os.Bundle;
9
+import android.os.IBinder;
10
+import android.support.annotation.Nullable;
11
+
12
+import net.typeblog.shelter.ShelterApplication;
13
+
14
+public class DummyActivity extends Activity {
15
+    @Override
16
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
17
+        super.onCreate(savedInstanceState);
18
+        ((ShelterApplication) getApplication()).bindShelterService(new ServiceConnection() {
19
+            @Override
20
+            public void onServiceConnected(ComponentName name, IBinder service) {
21
+                Intent data = new Intent();
22
+                Bundle bundle = new Bundle();
23
+                bundle.putBinder("service", service);
24
+                data.putExtra("extra", bundle);
25
+                setResult(RESULT_OK, data);
26
+                finish();
27
+            }
28
+
29
+            @Override
30
+            public void onServiceDisconnected(ComponentName name) {
31
+                // dummy
32
+            }
33
+        });
34
+    }
35
+}

+ 78
- 9
app/src/main/java/net/typeblog/shelter/ui/MainActivity.java View File

@@ -3,21 +3,32 @@ package net.typeblog.shelter.ui;
3 3
 import android.app.admin.DevicePolicyManager;
4 4
 import android.content.ComponentName;
5 5
 import android.content.Intent;
6
+import android.content.ServiceConnection;
7
+import android.os.IBinder;
8
+import android.os.RemoteException;
6 9
 import android.support.annotation.Nullable;
7 10
 import android.support.v7.app.AppCompatActivity;
8 11
 import android.os.Bundle;
9 12
 import android.widget.Toast;
10 13
 
11 14
 import net.typeblog.shelter.R;
15
+import net.typeblog.shelter.ShelterApplication;
12 16
 import net.typeblog.shelter.receivers.ShelterDeviceAdminReceiver;
17
+import net.typeblog.shelter.services.IShelterService;
13 18
 import net.typeblog.shelter.util.LocalStorageManager;
19
+import net.typeblog.shelter.util.Utility;
14 20
 
15 21
 public class MainActivity extends AppCompatActivity {
16 22
     private static final int REQUEST_PROVISION_PROFILE = 1;
23
+    private static final int REQUEST_START_SERVICE_IN_WORK_PROFILE = 2;
17 24
 
18 25
     private LocalStorageManager mStorage = null;
19 26
     private DevicePolicyManager mPolicyManager = null;
20 27
 
28
+    // Two services running in main / work profile
29
+    private IShelterService mServiceMain = null;
30
+    private IShelterService mServiceWork = null;
31
+
21 32
     @Override
22 33
     protected void onCreate(Bundle savedInstanceState) {
23 34
         super.onCreate(savedInstanceState);
@@ -28,6 +39,7 @@ public class MainActivity extends AppCompatActivity {
28 39
         if (mPolicyManager.isProfileOwnerApp(getPackageName())) {
29 40
             // We are now in our own profile
30 41
             // We should never start the main activity here.
42
+            android.util.Log.d("MainActivity", "started in user profile. stopping.");
31 43
             finish();
32 44
         } else {
33 45
             if (!mStorage.getBoolean(LocalStorageManager.PREF_IS_DEVICE_ADMIN)) {
@@ -39,14 +51,13 @@ public class MainActivity extends AppCompatActivity {
39 51
                 setupProfile();
40 52
             } else {
41 53
                 // Initialize the app
42
-                // we should bind to a service running in the work profile
43
-                // in order to get the application lists etc.
54
+                initializeApp();
44 55
             }
45 56
         }
46 57
 
47 58
     }
48 59
 
49
-    private boolean setupProfile() {
60
+    private void setupProfile() {
50 61
         // Check if provisioning is allowed
51 62
         if (!mPolicyManager.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE)) {
52 63
             Toast.makeText(this,
@@ -55,27 +66,85 @@ public class MainActivity extends AppCompatActivity {
55 66
         }
56 67
 
57 68
         // Start provisioning
69
+        ComponentName admin = new ComponentName(getApplicationContext(), ShelterDeviceAdminReceiver.class);
58 70
         Intent intent = new Intent(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE);
59
-
60 71
         intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION, true);
61
-        intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
62
-                new ComponentName(getApplicationContext(), ShelterDeviceAdminReceiver.class));
72
+        intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME, admin);
63 73
         startActivityForResult(intent, REQUEST_PROVISION_PROFILE);
74
+    }
75
+
76
+    private void initializeApp() {
77
+        // Bind to the service provided by this app in main user
78
+        ((ShelterApplication) getApplication()).bindShelterService(new ServiceConnection() {
79
+            @Override
80
+            public void onServiceConnected(ComponentName name, IBinder service) {
81
+                mServiceMain = IShelterService.Stub.asInterface(service);
82
+                bindWorkService();
83
+            }
84
+
85
+            @Override
86
+            public void onServiceDisconnected(ComponentName name) {
87
+                // dummy
88
+            }
89
+        });
90
+    }
91
+
92
+    private void bindWorkService() {
93
+        // Bind to the ShelterService in work profile
94
+        Intent intent = new Intent("net.typeblog.shelter.action.START_SERVICE");
95
+        intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
96
+        Utility.transferIntentToProfile(this, intent);
97
+        startActivityForResult(intent, REQUEST_START_SERVICE_IN_WORK_PROFILE);
98
+    }
64 99
 
65
-        // We should continue the setup process later when provision completed
66
-        return false;
100
+    private void buildView() {
101
+        // Finally we can build the view
102
+        // TODO: Actually implement this method
103
+        try {
104
+            android.util.Log.d("MainActivity", "Main profile app count: " + mServiceMain.getApps().size());
105
+            android.util.Log.d("MainActivity", "Work profile app count: " + mServiceWork.getApps().size());
106
+        } catch (Exception e) {
107
+
108
+        }
109
+    }
110
+
111
+    @Override
112
+    protected void onDestroy() {
113
+        super.onDestroy();
114
+
115
+        try {
116
+            // For the work instance, we just kill it entirely
117
+            // We don't need it to be awake to do anything useful
118
+            mServiceWork.stopShelterService(true);
119
+        } catch (RemoteException e) {
120
+            // We are stopping anyway
121
+        }
122
+
123
+        try {
124
+            mServiceMain.stopShelterService(false);
125
+        } catch (RemoteException e) {
126
+            // We are stopping anyway
127
+        }
67 128
     }
68 129
 
69 130
     @Override
70 131
     protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
71 132
         super.onActivityResult(requestCode, resultCode, data);
72 133
 
73
-        if (resultCode == REQUEST_PROVISION_PROFILE) {
134
+        if (requestCode == REQUEST_PROVISION_PROFILE && resultCode == RESULT_OK) {
74 135
             // Provisioning finished.
75 136
             // Set the HAS_SETUP flag
76 137
             mStorage.setBoolean(LocalStorageManager.PREF_HAS_SETUP, true);
77 138
 
78 139
             // Initialize the app just as if the activity was started.
140
+            // TODO: Should not initialize here. It is possible that the process is not finished yet.
141
+            //initializeApp();
142
+        } else if (requestCode == REQUEST_START_SERVICE_IN_WORK_PROFILE && resultCode == RESULT_OK) {
143
+            // TODO: Set the service in work profile as foreground to keep it alive
144
+            Bundle extra = data.getBundleExtra("extra");
145
+            IBinder binder = extra.getBinder("service");
146
+            mServiceWork = IShelterService.Stub.asInterface(binder);
147
+            buildView();
79 148
         }
80 149
     }
81 150
 }

+ 22
- 0
app/src/main/java/net/typeblog/shelter/util/Utility.java View File

@@ -0,0 +1,22 @@
1
+package net.typeblog.shelter.util;
2
+
3
+import android.content.ComponentName;
4
+import android.content.Context;
5
+import android.content.Intent;
6
+import android.content.pm.PackageManager;
7
+import android.content.pm.ResolveInfo;
8
+
9
+import java.util.List;
10
+import java.util.stream.Collectors;
11
+
12
+public class Utility {
13
+    // Affiliate an Intent to another profile (i.e. the Work profile that we manage)
14
+    public static void transferIntentToProfile(Context context, Intent intent) {
15
+        PackageManager pm = context.getPackageManager();
16
+        List<ResolveInfo> info = pm.queryIntentActivities(intent, 0);
17
+        ResolveInfo i = info.stream()
18
+                .filter((r) -> !r.activityInfo.packageName.equals(context.getPackageName()))
19
+                .collect(Collectors.toList()).get(0);
20
+        intent.setComponent(new ComponentName(i.activityInfo.packageName, i.activityInfo.name));
21
+    }
22
+}

Loading…
Cancel
Save