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