blob: e77e7ec468934684a0f03a13561c062fc06eb19c [file] [log] [blame]
/*
* Copyright 2014, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.managedprovisioning.task;
import static android.app.admin.DevicePolicyManager.ACTION_DATA_SHARING_RESTRICTION_CHANGED;
import static android.content.pm.PackageManager.ONLY_IF_NO_MATCH_FOUND;
import static android.content.pm.PackageManager.SKIP_CURRENT_PROFILE;
import static android.speech.RecognizerIntent.ACTION_RECOGNIZE_SPEECH;
import static com.android.internal.util.Preconditions.checkNotNull;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.hardware.usb.UsbManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.AlarmClock;
import android.provider.MediaStore;
import com.android.internal.annotations.VisibleForTesting;
import com.android.managedprovisioning.common.ProvisionLogger;
import com.android.managedprovisioning.task.CrossProfileIntentFilter.Direction;
import java.util.Arrays;
import java.util.List;
/**
* Class to set CrossProfileIntentFilters during managed profile creation, and reset them after an
* ota.
*/
public class CrossProfileIntentFiltersSetter {
// Intents from profile to parent user
/** Emergency call intent with mime type is always resolved by primary user. */
private static final CrossProfileIntentFilter EMERGENCY_CALL_MIME =
new CrossProfileIntentFilter.Builder(Direction.TO_PARENT, SKIP_CURRENT_PROFILE, false)
.addAction(Intent.ACTION_CALL_EMERGENCY)
.addAction(Intent.ACTION_CALL_PRIVILEGED)
.addCategory(Intent.CATEGORY_DEFAULT)
.addCategory(Intent.CATEGORY_BROWSABLE)
.addDataType("vnd.android.cursor.item/phone")
.addDataType("vnd.android.cursor.item/phone_v2")
.addDataType("vnd.android.cursor.item/person")
.addDataType("vnd.android.cursor.dir/calls")
.addDataType("vnd.android.cursor.item/calls")
.build();
/** Emergency call intent with data schemes is always resolved by primary user. */
private static final CrossProfileIntentFilter EMERGENCY_CALL_DATA =
new CrossProfileIntentFilter.Builder(Direction.TO_PARENT, SKIP_CURRENT_PROFILE, false)
.addAction(Intent.ACTION_CALL_EMERGENCY)
.addAction(Intent.ACTION_CALL_PRIVILEGED)
.addCategory(Intent.CATEGORY_DEFAULT)
.addCategory(Intent.CATEGORY_BROWSABLE)
.addDataScheme("tel")
.addDataScheme("sip")
.addDataScheme("voicemail")
.build();
/** Dial intent with mime type can be handled by either managed profile or its parent user. */
private static final CrossProfileIntentFilter DIAL_MIME =
new CrossProfileIntentFilter.Builder(Direction.TO_PARENT, ONLY_IF_NO_MATCH_FOUND, false)
.addAction(Intent.ACTION_DIAL)
.addAction(Intent.ACTION_VIEW)
.addCategory(Intent.CATEGORY_DEFAULT)
.addCategory(Intent.CATEGORY_BROWSABLE)
.addDataType("vnd.android.cursor.item/phone")
.addDataType("vnd.android.cursor.item/phone_v2")
.addDataType("vnd.android.cursor.item/person")
.addDataType("vnd.android.cursor.dir/calls")
.addDataType("vnd.android.cursor.item/calls")
.build();
/** Dial intent with data scheme can be handled by either managed profile or its parent user. */
private static final CrossProfileIntentFilter DIAL_DATA =
new CrossProfileIntentFilter.Builder(Direction.TO_PARENT, ONLY_IF_NO_MATCH_FOUND, false)
.addAction(Intent.ACTION_DIAL)
.addAction(Intent.ACTION_VIEW)
.addCategory(Intent.CATEGORY_DEFAULT)
.addCategory(Intent.CATEGORY_BROWSABLE)
.addDataScheme("tel")
.addDataScheme("sip")
.addDataScheme("voicemail")
.build();
/**
* Dial intent with no data scheme or type can be handled by either managed profile or its
* parent user.
*/
private static final CrossProfileIntentFilter DIAL_RAW =
new CrossProfileIntentFilter.Builder(Direction.TO_PARENT, ONLY_IF_NO_MATCH_FOUND, false)
.addAction(Intent.ACTION_DIAL)
.addCategory(Intent.CATEGORY_DEFAULT)
.addCategory(Intent.CATEGORY_BROWSABLE)
.build();
/** Pressing the call button can be handled by either managed profile or its parent user. */
private static final CrossProfileIntentFilter CALL_BUTTON =
new CrossProfileIntentFilter.Builder(Direction.TO_PARENT, ONLY_IF_NO_MATCH_FOUND, false)
.addAction(Intent.ACTION_CALL_BUTTON)
.addCategory(Intent.CATEGORY_DEFAULT)
.build();
/** SMS and MMS are exclusively handled by the primary user. */
private static final CrossProfileIntentFilter SMS_MMS =
new CrossProfileIntentFilter.Builder(Direction.TO_PARENT, SKIP_CURRENT_PROFILE, false)
.addAction(Intent.ACTION_VIEW)
.addAction(Intent.ACTION_SENDTO)
.addCategory(Intent.CATEGORY_DEFAULT)
.addCategory(Intent.CATEGORY_BROWSABLE)
.addDataScheme("sms")
.addDataScheme("smsto")
.addDataScheme("mms")
.addDataScheme("mmsto")
.build();
/** Mobile network settings is always shown in the primary user. */
private static final CrossProfileIntentFilter MOBILE_NETWORK_SETTINGS =
new CrossProfileIntentFilter.Builder(Direction.TO_PARENT, SKIP_CURRENT_PROFILE, false)
.addAction(android.provider.Settings.ACTION_DATA_ROAMING_SETTINGS)
.addAction(android.provider.Settings.ACTION_NETWORK_OPERATOR_SETTINGS)
.addCategory(Intent.CATEGORY_DEFAULT)
.build();
/** HOME intent is always resolved by the primary user. */
@VisibleForTesting
static final CrossProfileIntentFilter HOME =
new CrossProfileIntentFilter.Builder(Direction.TO_PARENT, SKIP_CURRENT_PROFILE, false)
.addAction(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_DEFAULT)
.addCategory(Intent.CATEGORY_HOME)
.build();
/** Get content can be forwarded to parent user. */
private static final CrossProfileIntentFilter GET_CONTENT =
new CrossProfileIntentFilter.Builder(Direction.TO_PARENT, 0, true)
.addAction(Intent.ACTION_GET_CONTENT)
.addCategory(Intent.CATEGORY_DEFAULT)
.addCategory(Intent.CATEGORY_OPENABLE)
.addDataType("*/*")
.build();
/** Open document intent can be forwarded to parent user. */
private static final CrossProfileIntentFilter OPEN_DOCUMENT =
new CrossProfileIntentFilter.Builder(Direction.TO_PARENT, 0, true)
.addAction(Intent.ACTION_OPEN_DOCUMENT)
.addCategory(Intent.CATEGORY_DEFAULT)
.addCategory(Intent.CATEGORY_OPENABLE)
.addDataType("*/*")
.build();
/** Pick for any data type can be forwarded to parent user. */
private static final CrossProfileIntentFilter ACTION_PICK_DATA =
new CrossProfileIntentFilter.Builder(Direction.TO_PARENT, 0, true)
.addAction(Intent.ACTION_PICK)
.addCategory(Intent.CATEGORY_DEFAULT)
.addDataType("*/*")
.build();
/** Pick without data type can be forwarded to parent user. */
private static final CrossProfileIntentFilter ACTION_PICK_RAW =
new CrossProfileIntentFilter.Builder(Direction.TO_PARENT, 0, true)
.addAction(Intent.ACTION_PICK)
.addCategory(Intent.CATEGORY_DEFAULT)
.build();
/** Speech recognition can be performed by primary user. */
private static final CrossProfileIntentFilter RECOGNIZE_SPEECH =
new CrossProfileIntentFilter.Builder(Direction.TO_PARENT, 0, false)
.addAction(ACTION_RECOGNIZE_SPEECH)
.addCategory(Intent.CATEGORY_DEFAULT)
.build();
/** Media capture can be performed by primary user. */
private static final CrossProfileIntentFilter MEDIA_CAPTURE =
new CrossProfileIntentFilter.Builder(Direction.TO_PARENT, 0, true)
.addAction(MediaStore.ACTION_IMAGE_CAPTURE)
.addAction(MediaStore.ACTION_IMAGE_CAPTURE_SECURE)
.addAction(MediaStore.ACTION_VIDEO_CAPTURE)
.addAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION)
.addAction(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA)
.addAction(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE)
.addAction(MediaStore.INTENT_ACTION_VIDEO_CAMERA)
.addCategory(Intent.CATEGORY_DEFAULT)
.build();
/** Alarm setting can be performed by primary user. */
private static final CrossProfileIntentFilter SET_ALARM =
new CrossProfileIntentFilter.Builder(Direction.TO_PARENT, 0, false)
.addAction(AlarmClock.ACTION_SET_ALARM)
.addAction(AlarmClock.ACTION_SHOW_ALARMS)
.addAction(AlarmClock.ACTION_SET_TIMER)
.addCategory(Intent.CATEGORY_DEFAULT)
.build();
// Intents from parent to profile user
/** ACTION_SEND can be forwarded to the managed profile on user's choice. */
@VisibleForTesting
static final CrossProfileIntentFilter ACTION_SEND =
new CrossProfileIntentFilter.Builder(Direction.TO_PROFILE, 0, true)
.addAction(Intent.ACTION_SEND)
.addAction(Intent.ACTION_SEND_MULTIPLE)
.addCategory(Intent.CATEGORY_DEFAULT)
.addDataType("*/*")
.build();
/** USB devices attached can get forwarded to the profile. */
private static final CrossProfileIntentFilter USB_DEVICE_ATTACHED =
new CrossProfileIntentFilter.Builder(Direction.TO_PROFILE, 0, false)
.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED)
.addAction(UsbManager.ACTION_USB_ACCESSORY_ATTACHED)
.addCategory(Intent.CATEGORY_DEFAULT)
.build();
@VisibleForTesting
static final List<CrossProfileIntentFilter> FILTERS = Arrays.asList(
EMERGENCY_CALL_MIME,
EMERGENCY_CALL_DATA,
DIAL_MIME,
DIAL_DATA,
DIAL_RAW,
CALL_BUTTON,
SMS_MMS,
SET_ALARM,
MEDIA_CAPTURE,
RECOGNIZE_SPEECH,
ACTION_PICK_RAW,
ACTION_PICK_DATA,
OPEN_DOCUMENT,
GET_CONTENT,
USB_DEVICE_ATTACHED,
ACTION_SEND,
HOME,
MOBILE_NETWORK_SETTINGS);
/**
* Broadcast receiver for DevicePolicyManager.ACTION_DATA_SHARING_RESTRICTION_CHANGED
*/
public static class RestrictionChangedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (!ACTION_DATA_SHARING_RESTRICTION_CHANGED.equals(intent.getAction())) {
return;
}
int profileUser = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL);
if (profileUser == UserHandle.USER_NULL) {
return;
}
UserInfo parent = context.getSystemService(UserManager.class)
.getProfileParent(profileUser);
if (parent == null) {
return;
}
// Always call resetFilters() on the parent user, which handles cross profile
// intent filters between the parent and its profiles.
ProvisionLogger.logd("Resetting cross-profile intent filters on restriction change");
new CrossProfileIntentFiltersSetter(context).resetFilters(parent.id);
context.sendBroadcastAsUser(new Intent(
DevicePolicyManager.ACTION_DATA_SHARING_RESTRICTION_APPLIED),
UserHandle.of(profileUser));
}
}
private final PackageManager mPackageManager;
private final UserManager mUserManager;
public CrossProfileIntentFiltersSetter(Context context) {
this(context.getPackageManager(),
(UserManager) context.getSystemService(Context.USER_SERVICE));
}
@VisibleForTesting
CrossProfileIntentFiltersSetter(PackageManager packageManager, UserManager userManager) {
mPackageManager = checkNotNull(packageManager);
mUserManager = checkNotNull(userManager);
}
/**
* Sets all default cross profile intent filters from {@code parentUserId} to
* {@code managedProfileUserId}.
*/
public void setFilters(int parentUserId, int managedProfileUserId) {
ProvisionLogger.logd("Setting cross-profile intent filters");
boolean disallowSharingIntoProfile = mUserManager.hasUserRestriction(
UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE,
UserHandle.of(managedProfileUserId));
for (CrossProfileIntentFilter filter : FILTERS) {
// Skip filters that allow data to be shared into the profile, if admin has disabled
// it.
if (disallowSharingIntoProfile && filter.letsPersonalDataIntoProfile) {
continue;
}
if (filter.direction == Direction.TO_PARENT) {
mPackageManager.addCrossProfileIntentFilter(filter.filter, managedProfileUserId,
parentUserId, filter.flags);
} else {
mPackageManager.addCrossProfileIntentFilter(filter.filter, parentUserId,
managedProfileUserId, filter.flags);
}
}
}
/**
* Reset the cross profile intent filters between {@code userId} and all of its managed profiles
* if any.
*/
public void resetFilters(int userId) {
List<UserInfo> profiles = mUserManager.getProfiles(userId);
if (profiles.size() <= 1) {
return;
}
// Removes cross profile intent filters from the parent to all the managed profiles.
mPackageManager.clearCrossProfileIntentFilters(userId);
// For each managed profile reset cross profile intent filters
for (UserInfo profile : profiles) {
if (!profile.isManagedProfile()) {
continue;
}
mPackageManager.clearCrossProfileIntentFilters(profile.id);
setFilters(userId, profile.id);
}
}
}