1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.cts.verifier.managedprovisioning;
18 
19 import android.app.Activity;
20 import android.app.admin.DevicePolicyManager;
21 import android.app.Dialog;
22 import android.content.BroadcastReceiver;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.content.pm.PackageManager;
28 import android.net.Uri;
29 import android.os.Bundle;
30 import android.os.UserManager;
31 import android.provider.MediaStore;
32 import android.provider.Settings;
33 import android.support.v4.content.FileProvider;
34 import android.util.Log;
35 import android.widget.Toast;
36 
37 import static android.provider.Settings.Secure.INSTALL_NON_MARKET_APPS;
38 
39 import java.io.File;
40 import java.util.ArrayList;
41 
42 import com.android.cts.verifier.R;
43 import com.android.cts.verifier.managedprovisioning.ByodPresentMediaDialog.DialogCallback;
44 
45 /**
46  * A helper activity from the managed profile side that responds to requests from CTS verifier in
47  * primary user. Profile owner APIs are accessible inside this activity (given this activity is
48  * started within the work profile). Its current functionalities include making sure the profile
49  * owner is setup correctly, removing the work profile upon request, and verifying the image and
50  * video capture functionality.
51  *
52  * Note: We have to use a dummy activity because cross-profile intents only work for activities.
53  */
54 public class ByodHelperActivity extends Activity implements DialogCallback {
55     static final String TAG = "ByodHelperActivity";
56 
57     // Primary -> managed intent: query if the profile owner has been set up.
58     public static final String ACTION_QUERY_PROFILE_OWNER = "com.android.cts.verifier.managedprovisioning.BYOD_QUERY";
59     // Managed -> primary intent: update profile owner test status in primary's CtsVerifer
60     public static final String ACTION_PROFILE_OWNER_STATUS = "com.android.cts.verifier.managedprovisioning.BYOD_STATUS";
61     // Primary -> managed intent: request to delete the current profile
62     public static final String ACTION_REMOVE_PROFILE_OWNER = "com.android.cts.verifier.managedprovisioning.BYOD_REMOVE";
63     // Managed -> managed intent: provisioning completed successfully
64     public static final String ACTION_PROFILE_PROVISIONED = "com.android.cts.verifier.managedprovisioning.BYOD_PROVISIONED";
65     // Primage -> managed intent: request to capture and check an image
66     public static final String ACTION_CAPTURE_AND_CHECK_IMAGE = "com.android.cts.verifier.managedprovisioning.BYOD_CAPTURE_AND_CHECK_IMAGE";
67     // Primage -> managed intent: request to capture and check a video
68     public static final String ACTION_CAPTURE_AND_CHECK_VIDEO = "com.android.cts.verifier.managedprovisioning.BYOD_CAPTURE_AND_CHECK_VIDEO";
69     // Primage -> managed intent: request to capture and check an audio recording
70     public static final String ACTION_CAPTURE_AND_CHECK_AUDIO = "com.android.cts.verifier.managedprovisioning.BYOD_CAPTURE_AND_CHECK_AUDIO";
71     public static final String ACTION_KEYGUARD_DISABLED_FEATURES =
72             "com.android.cts.verifier.managedprovisioning.BYOD_KEYGUARD_DISABLED_FEATURES";
73     public static final String ACTION_LOCKNOW =
74             "com.android.cts.verifier.managedprovisioning.BYOD_LOCKNOW";
75     public static final String ACTION_TEST_NFC_BEAM = "com.android.cts.verifier.managedprovisioning.TEST_NFC_BEAM";
76 
77     public static final String EXTRA_PROVISIONED = "extra_provisioned";
78     public static final String EXTRA_PARAMETER_1 = "extra_parameter_1";
79 
80     // Primary -> managed intent: set unknown sources restriction and install package
81     public static final String ACTION_INSTALL_APK = "com.android.cts.verifier.managedprovisioning.BYOD_INSTALL_APK";
82     public static final String EXTRA_ALLOW_NON_MARKET_APPS = INSTALL_NON_MARKET_APPS;
83 
84     // Primary -> managed intent: check if the required cross profile intent filters are set.
85     public static final String ACTION_CHECK_INTENT_FILTERS =
86             "com.android.cts.verifier.managedprovisioning.action.CHECK_INTENT_FILTERS";
87 
88     // Primary -> managed intent: will send a cross profile intent and check if the user sees an
89     // intent picker dialog and can open the apps.
90     public static final String ACTION_TEST_CROSS_PROFILE_INTENTS_DIALOG =
91             "com.android.cts.verifier.managedprovisioning.action.TEST_CROSS_PROFILE_INTENTS_DIALOG";
92 
93     // Primary -> managed intent: will send an app link intent and check if the user sees a
94     // dialog and can open the apps. This test is extremely similar to
95     // ACTION_TEST_CROSS_PROFILE_INTENTS_DIALOG, but the intent used is a web intent, and there is
96     // some behavior which is specific to web intents.
97     public static final String ACTION_TEST_APP_LINKING_DIALOG =
98             "com.android.cts.verifier.managedprovisioning.action.TEST_APP_LINKING_DIALOG";
99 
100     public static final int RESULT_FAILED = RESULT_FIRST_USER;
101 
102     private static final int REQUEST_INSTALL_PACKAGE = 1;
103     private static final int REQUEST_IMAGE_CAPTURE = 2;
104     private static final int REQUEST_VIDEO_CAPTURE = 3;
105     private static final int REQUEST_AUDIO_CAPTURE = 4;
106 
107     private static final String ORIGINAL_SETTINGS_NAME = "original settings";
108     private Bundle mOriginalSettings;
109 
110     private ComponentName mAdminReceiverComponent;
111     private DevicePolicyManager mDevicePolicyManager;
112 
113     private Uri mImageUri;
114     private Uri mVideoUri;
115 
116     private ArrayList<File> mTempFiles = new ArrayList<File>();
117 
118     @Override
onCreate(Bundle savedInstanceState)119     protected void onCreate(Bundle savedInstanceState) {
120         super.onCreate(savedInstanceState);
121         if (savedInstanceState != null) {
122             Log.w(TAG, "Restored state");
123             mOriginalSettings = savedInstanceState.getBundle(ORIGINAL_SETTINGS_NAME);
124         } else {
125             mOriginalSettings = new Bundle();
126         }
127 
128         mAdminReceiverComponent = new ComponentName(this, DeviceAdminTestReceiver.class.getName());
129         mDevicePolicyManager = (DevicePolicyManager) getSystemService(
130                 Context.DEVICE_POLICY_SERVICE);
131         Intent intent = getIntent();
132         String action = intent.getAction();
133         Log.d(TAG, "ByodHelperActivity.onCreate: " + action);
134 
135         // we are explicitly started by {@link DeviceAdminTestReceiver} after a successful provisioning.
136         if (action.equals(ACTION_PROFILE_PROVISIONED)) {
137             // Jump back to CTS verifier with result.
138             Intent response = new Intent(ACTION_PROFILE_OWNER_STATUS);
139             response.putExtra(EXTRA_PROVISIONED, isProfileOwner());
140             startActivityInPrimary(response);
141             // Queried by CtsVerifier in the primary side using startActivityForResult.
142         } else if (action.equals(ACTION_QUERY_PROFILE_OWNER)) {
143             Intent response = new Intent();
144             response.putExtra(EXTRA_PROVISIONED, isProfileOwner());
145             setResult(RESULT_OK, response);
146             // Request to delete work profile.
147         } else if (action.equals(ACTION_REMOVE_PROFILE_OWNER)) {
148             if (isProfileOwner()) {
149                 mDevicePolicyManager.wipeData(0);
150                 showToast(R.string.provisioning_byod_profile_deleted);
151             }
152         } else if (action.equals(ACTION_INSTALL_APK)) {
153             boolean allowNonMarket = intent.getBooleanExtra(EXTRA_ALLOW_NON_MARKET_APPS, false);
154             boolean wasAllowed = getAllowNonMarket();
155 
156             // Update permission to install non-market apps
157             setAllowNonMarket(allowNonMarket);
158             mOriginalSettings.putBoolean(INSTALL_NON_MARKET_APPS, wasAllowed);
159 
160             // Request to install a non-market application- easiest way is to reinstall ourself
161             final Intent installIntent = new Intent(Intent.ACTION_INSTALL_PACKAGE)
162                     .setData(Uri.parse("package:" + getPackageName()))
163                     .putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
164                     .putExtra(Intent.EXTRA_RETURN_RESULT, true);
165             startActivityForResult(installIntent, REQUEST_INSTALL_PACKAGE);
166 
167             // Not yet ready to finish- wait until the result comes back
168             return;
169             // Queried by CtsVerifier in the primary side using startActivityForResult.
170         } else if (action.equals(ACTION_CHECK_INTENT_FILTERS)) {
171             final boolean intentFiltersSetForManagedIntents =
172                     new IntentFiltersTestHelper(this).checkCrossProfileIntentFilters(
173                             IntentFiltersTestHelper.FLAG_INTENTS_FROM_MANAGED);
174             setResult(intentFiltersSetForManagedIntents? RESULT_OK : RESULT_FAILED, null);
175         } else if (action.equals(ACTION_CAPTURE_AND_CHECK_IMAGE)) {
176             // We need the camera permission to send the image capture intent.
177             grantCameraPermissionToSelf();
178             Intent captureImageIntent = getCaptureImageIntent();
179             mImageUri = getTempUri("image.jpg");
180             captureImageIntent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);
181             if (captureImageIntent.resolveActivity(getPackageManager()) != null) {
182                 startActivityForResult(captureImageIntent, REQUEST_IMAGE_CAPTURE);
183             } else {
184                 Log.e(TAG, "Capture image intent could not be resolved in managed profile.");
185                 showToast(R.string.provisioning_byod_capture_media_error);
186                 finish();
187             }
188             return;
189         } else if (action.equals(ACTION_CAPTURE_AND_CHECK_VIDEO)) {
190             // We need the camera permission to send the video capture intent.
191             grantCameraPermissionToSelf();
192             Intent captureVideoIntent = getCaptureVideoIntent();
193             mVideoUri = getTempUri("video.mp4");
194             captureVideoIntent.putExtra(MediaStore.EXTRA_OUTPUT, mVideoUri);
195             if (captureVideoIntent.resolveActivity(getPackageManager()) != null) {
196                 startActivityForResult(captureVideoIntent, REQUEST_VIDEO_CAPTURE);
197             } else {
198                 Log.e(TAG, "Capture video intent could not be resolved in managed profile.");
199                 showToast(R.string.provisioning_byod_capture_media_error);
200                 finish();
201             }
202             return;
203         } else if (action.equals(ACTION_CAPTURE_AND_CHECK_AUDIO)) {
204             Intent captureAudioIntent = getCaptureAudioIntent();
205             if (captureAudioIntent.resolveActivity(getPackageManager()) != null) {
206                 startActivityForResult(captureAudioIntent, REQUEST_AUDIO_CAPTURE);
207             } else {
208                 Log.e(TAG, "Capture audio intent could not be resolved in managed profile.");
209                 showToast(R.string.provisioning_byod_capture_media_error);
210                 finish();
211             }
212             return;
213         } else if (ACTION_KEYGUARD_DISABLED_FEATURES.equals(action)) {
214             final int value = intent.getIntExtra(EXTRA_PARAMETER_1,
215                     DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE);
216             ComponentName admin = DeviceAdminTestReceiver.getReceiverComponentName();
217             mDevicePolicyManager.setKeyguardDisabledFeatures(admin, value);
218         } else if (ACTION_LOCKNOW.equals(action)) {
219             mDevicePolicyManager.lockNow();
220             setResult(RESULT_OK);
221         } else if (action.equals(ACTION_TEST_NFC_BEAM)) {
222             Intent testNfcBeamIntent = new Intent(this, NfcTestActivity.class);
223             testNfcBeamIntent.putExtras(intent);
224             startActivity(testNfcBeamIntent);
225             finish();
226             return;
227         } else if (action.equals(ACTION_TEST_CROSS_PROFILE_INTENTS_DIALOG)) {
228             sendIntentInsideChooser(new Intent(
229                     CrossProfileTestActivity.ACTION_CROSS_PROFILE_TO_PERSONAL));
230         } else if (action.equals(ACTION_TEST_APP_LINKING_DIALOG)) {
231             mDevicePolicyManager.addUserRestriction(
232                     DeviceAdminTestReceiver.getReceiverComponentName(),
233                     UserManager.ALLOW_PARENT_PROFILE_APP_LINKING);
234             Intent toSend = new Intent(Intent.ACTION_VIEW);
235             toSend.setData(Uri.parse("http://com.android.cts.verifier"));
236             sendIntentInsideChooser(toSend);
237         }
238         // This activity has no UI and is only used to respond to CtsVerifier in the primary side.
239         finish();
240     }
241 
242     @Override
onSaveInstanceState(final Bundle savedState)243     protected void onSaveInstanceState(final Bundle savedState) {
244         super.onSaveInstanceState(savedState);
245 
246         savedState.putBundle(ORIGINAL_SETTINGS_NAME, mOriginalSettings);
247     }
248 
249     @Override
onActivityResult(int requestCode, int resultCode, Intent data)250     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
251         switch (requestCode) {
252             case REQUEST_INSTALL_PACKAGE: {
253                 Log.w(TAG, "Received REQUEST_INSTALL_PACKAGE, resultCode = " + resultCode);
254                 if (mOriginalSettings.containsKey(INSTALL_NON_MARKET_APPS)) {
255                     // Restore original setting
256                     setAllowNonMarket(mOriginalSettings.getBoolean(INSTALL_NON_MARKET_APPS));
257                     mOriginalSettings.remove(INSTALL_NON_MARKET_APPS);
258                 }
259                 finish();
260                 break;
261             }
262             case REQUEST_IMAGE_CAPTURE: {
263                 if (resultCode == RESULT_OK) {
264                     ByodPresentMediaDialog.newImageInstance(mImageUri)
265                             .show(getFragmentManager(), "ViewImageDialogFragment");
266                 } else {
267                     // Failed capturing image.
268                     finish();
269                 }
270                 break;
271             }
272             case REQUEST_VIDEO_CAPTURE: {
273                 if (resultCode == RESULT_OK) {
274                     ByodPresentMediaDialog.newVideoInstance(mVideoUri)
275                             .show(getFragmentManager(), "PlayVideoDialogFragment");
276                 } else {
277                     // Failed capturing video.
278                     finish();
279                 }
280                 break;
281             }
282             case REQUEST_AUDIO_CAPTURE: {
283                 if (resultCode == RESULT_OK) {
284                     ByodPresentMediaDialog.newAudioInstance(data.getData())
285                             .show(getFragmentManager(), "PlayAudioDialogFragment");
286                 } else {
287                     // Failed capturing audio.
288                     finish();
289                 }
290                 break;
291             }
292             default: {
293                 Log.wtf(TAG, "Unknown requestCode " + requestCode + "; data = " + data);
294                 break;
295             }
296         }
297     }
298 
299     @Override
onDestroy()300     protected void onDestroy() {
301         cleanUpTempUris();
302         super.onDestroy();
303     }
304 
getCaptureImageIntent()305     public static Intent getCaptureImageIntent() {
306         return new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
307     }
308 
getCaptureVideoIntent()309     public static Intent getCaptureVideoIntent() {
310         return new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
311     }
312 
getCaptureAudioIntent()313     public static Intent getCaptureAudioIntent() {
314         return new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
315     }
316 
createLockIntent()317     public static Intent createLockIntent() {
318         return new Intent(ACTION_LOCKNOW);
319     }
320 
getTempUri(String fileName)321     private Uri getTempUri(String fileName) {
322         final File file = new File(getFilesDir() + File.separator + "images"
323                 + File.separator + fileName);
324         file.getParentFile().mkdirs(); //if the folder doesn't exists it is created
325         mTempFiles.add(file);
326         return FileProvider.getUriForFile(this,
327                     "com.android.cts.verifier.managedprovisioning.fileprovider", file);
328     }
329 
cleanUpTempUris()330     private void cleanUpTempUris() {
331         for (File file : mTempFiles) {
332             file.delete();
333         }
334     }
335 
isProfileOwner()336     private boolean isProfileOwner() {
337         return mDevicePolicyManager.isAdminActive(mAdminReceiverComponent) &&
338                 mDevicePolicyManager.isProfileOwnerApp(mAdminReceiverComponent.getPackageName());
339     }
340 
getAllowNonMarket()341     private boolean getAllowNonMarket() {
342         String value = Settings.Secure.getString(getContentResolver(), INSTALL_NON_MARKET_APPS);
343         return "1".equals(value);
344     }
345 
setAllowNonMarket(boolean allow)346     private void setAllowNonMarket(boolean allow) {
347         mDevicePolicyManager.setSecureSetting(mAdminReceiverComponent, INSTALL_NON_MARKET_APPS,
348                 (allow ? "1" : "0"));
349     }
350 
startActivityInPrimary(Intent intent)351     private void startActivityInPrimary(Intent intent) {
352         // Disable app components in the current profile, so only the counterpart in the other
353         // profile can respond (via cross-profile intent filter)
354         getPackageManager().setComponentEnabledSetting(new ComponentName(
355                 this, ByodFlowTestActivity.class),
356                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
357                 PackageManager.DONT_KILL_APP);
358         startActivity(intent);
359     }
360 
showToast(int messageId)361     private void showToast(int messageId) {
362         String message = getString(messageId);
363         Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
364     }
365 
grantCameraPermissionToSelf()366     private void grantCameraPermissionToSelf() {
367         mDevicePolicyManager.setPermissionGrantState(mAdminReceiverComponent, getPackageName(),
368                 android.Manifest.permission.CAMERA,
369                 DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
370     }
371 
sendIntentInsideChooser(Intent toSend)372     private void sendIntentInsideChooser(Intent toSend) {
373         toSend.putExtra(CrossProfileTestActivity.EXTRA_STARTED_FROM_WORK, true);
374         Intent chooser = Intent.createChooser(toSend,
375                 getResources().getString(R.string.provisioning_cross_profile_chooser));
376         startActivity(chooser);
377     }
378 
379     @Override
onDialogClose()380     public void onDialogClose() {
381         finish();
382     }
383 }
384