summaryrefslogtreecommitdiff
path: root/src/com/android/providers/calendar/enterprise/CrossProfileCalendarHelper.java
blob: 8c5ace72e859cf92795721d161adb26f2bb25fad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
/*
 * Copyright (C) 2018 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.providers.calendar.enterprise;

import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.UserHandle;
import android.provider.CalendarContract;
import android.provider.Settings;
import android.util.ArraySet;
import android.util.Log;

import java.util.Set;

/**
 * Helper class for cross profile calendar related policies.
 */
public class CrossProfileCalendarHelper {

    private static final String LOG_TAG = "CrossProfileCalendarHelper";

    final private Context mContext;

    public static final Set<String> EVENTS_TABLE_ALLOWED_LIST;
    public static final Set<String> CALENDARS_TABLE_ALLOWED_LIST;
    public static final Set<String> INSTANCES_TABLE_ALLOWED_LIST;

    static {
        EVENTS_TABLE_ALLOWED_LIST = new ArraySet<>();
        EVENTS_TABLE_ALLOWED_LIST.add(CalendarContract.Events._ID);
        EVENTS_TABLE_ALLOWED_LIST.add(CalendarContract.Events.CALENDAR_ID);
        EVENTS_TABLE_ALLOWED_LIST.add(CalendarContract.Events.TITLE);
        EVENTS_TABLE_ALLOWED_LIST.add(CalendarContract.Events.EVENT_LOCATION);
        EVENTS_TABLE_ALLOWED_LIST.add(CalendarContract.Events.EVENT_COLOR);
        EVENTS_TABLE_ALLOWED_LIST.add(CalendarContract.Events.STATUS);
        EVENTS_TABLE_ALLOWED_LIST.add(CalendarContract.Events.DTSTART);
        EVENTS_TABLE_ALLOWED_LIST.add(CalendarContract.Events.DTEND);
        EVENTS_TABLE_ALLOWED_LIST.add(CalendarContract.Events.EVENT_TIMEZONE);
        EVENTS_TABLE_ALLOWED_LIST.add(CalendarContract.Events.EVENT_END_TIMEZONE);
        EVENTS_TABLE_ALLOWED_LIST.add(CalendarContract.Events.DURATION);
        EVENTS_TABLE_ALLOWED_LIST.add(CalendarContract.Events.ALL_DAY);
        EVENTS_TABLE_ALLOWED_LIST.add(CalendarContract.Events.AVAILABILITY);
        EVENTS_TABLE_ALLOWED_LIST.add(CalendarContract.Events.RRULE);
        EVENTS_TABLE_ALLOWED_LIST.add(CalendarContract.Events.RDATE);
        EVENTS_TABLE_ALLOWED_LIST.add(CalendarContract.Events.EXRULE);
        EVENTS_TABLE_ALLOWED_LIST.add(CalendarContract.Events.EXDATE);
        EVENTS_TABLE_ALLOWED_LIST.add(CalendarContract.Events.LAST_DATE);
        EVENTS_TABLE_ALLOWED_LIST.add(CalendarContract.Events.SELF_ATTENDEE_STATUS);
        EVENTS_TABLE_ALLOWED_LIST.add(CalendarContract.Events.DISPLAY_COLOR);

        CALENDARS_TABLE_ALLOWED_LIST = new ArraySet<>();
        CALENDARS_TABLE_ALLOWED_LIST.add(CalendarContract.Calendars._ID);
        CALENDARS_TABLE_ALLOWED_LIST.add(CalendarContract.Calendars.CALENDAR_COLOR);
        CALENDARS_TABLE_ALLOWED_LIST.add(CalendarContract.Calendars.VISIBLE);
        CALENDARS_TABLE_ALLOWED_LIST.add(CalendarContract.Calendars.CALENDAR_LOCATION);
        CALENDARS_TABLE_ALLOWED_LIST.add(CalendarContract.Calendars.CALENDAR_TIME_ZONE);
        CALENDARS_TABLE_ALLOWED_LIST.add(CalendarContract.Calendars.IS_PRIMARY);

        INSTANCES_TABLE_ALLOWED_LIST = new ArraySet<>();
        INSTANCES_TABLE_ALLOWED_LIST.add(CalendarContract.Instances._ID);
        INSTANCES_TABLE_ALLOWED_LIST.add(CalendarContract.Instances.EVENT_ID);
        INSTANCES_TABLE_ALLOWED_LIST.add(CalendarContract.Instances.BEGIN);
        INSTANCES_TABLE_ALLOWED_LIST.add(CalendarContract.Instances.END);
        INSTANCES_TABLE_ALLOWED_LIST.add(CalendarContract.Instances.START_DAY);
        INSTANCES_TABLE_ALLOWED_LIST.add(CalendarContract.Instances.END_DAY);
        INSTANCES_TABLE_ALLOWED_LIST.add(CalendarContract.Instances.START_MINUTE);
        INSTANCES_TABLE_ALLOWED_LIST.add(CalendarContract.Instances.END_MINUTE);

        // Add calendar columns.
        EVENTS_TABLE_ALLOWED_LIST.add(CalendarContract.Calendars.CALENDAR_COLOR);
        EVENTS_TABLE_ALLOWED_LIST.add(CalendarContract.Calendars.VISIBLE);
        EVENTS_TABLE_ALLOWED_LIST.add(CalendarContract.Calendars.CALENDAR_TIME_ZONE);
        EVENTS_TABLE_ALLOWED_LIST.add(CalendarContract.Calendars.IS_PRIMARY);

        ((ArraySet<String>) INSTANCES_TABLE_ALLOWED_LIST).addAll(EVENTS_TABLE_ALLOWED_LIST);
    }

    public CrossProfileCalendarHelper(Context context) {
        mContext = context;
    }

    /**
     * @return a context created from the given context for the given user, or null if it fails.
     */
    private Context createPackageContextAsUser(Context context, int userId) {
        try {
            return context.createPackageContextAsUser(
                    context.getPackageName(), 0 /* flags */, UserHandle.of(userId));
        } catch (PackageManager.NameNotFoundException e) {
            Log.e(LOG_TAG, "Failed to create user context", e);
        }
        return null;
    }

    /**
     * Returns whether a package is allowed to access cross-profile calendar APIs.
     *
     * A package is allowed to access cross-profile calendar APIs if it's allowed by the
     * profile owner of a managed profile to access the managed profile calendar provider,
     * and the setting {@link Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED} is turned
     * on in the managed profile.
     *
     * @param packageName  the name of the package
     * @param managedProfileUserId the user id of the managed profile
     * @return {@code true} if the package is allowed, {@false} otherwise.
     */
    public boolean isPackageAllowedToAccessCalendar(String packageName, int managedProfileUserId) {
        final Context managedProfileUserContext = createPackageContextAsUser(
                mContext, managedProfileUserId);
        final DevicePolicyManager mDpm = managedProfileUserContext.getSystemService(
                DevicePolicyManager.class);
        return mDpm.isPackageAllowedToAccessCalendar(packageName);
    }

    private static void ensureProjectionAllowed(String[] projection, Set<String> validColumnsSet) {
        for (String column : projection) {
            if (!validColumnsSet.contains(column)) {
                throw new IllegalArgumentException(String.format("Column %s is not "
                        + "allowed to be accessed from cross profile Uris", column));
            }
        }
    }

    /**
     * Returns the calibrated version of projection for a given table.
     *
     * If the input projection is empty, return an array of all the allowed list of columns for a
     * given table. Table is determined by the input uri.
     *
     * @param projection the original projection
     * @param localUri the local uri for the query of the projection
     * @return the original value of the input projection if it's not empty, otherwise an array of
     * all the allowed list of columns.
     * @throws IllegalArgumentException if the input projection contains a column that is not
     * in the allowed list for a given table.
     */
    public String[] getCalibratedProjection(String[] projection, Uri localUri) {
        // If projection is not empty, check if it's valid. Otherwise fill it with all
        // allowed columns.
        Set<String> validColumnsSet = new ArraySet<String>();
        if (CalendarContract.Events.CONTENT_URI.equals(localUri)) {
            validColumnsSet = EVENTS_TABLE_ALLOWED_LIST;
        } else if (CalendarContract.Calendars.CONTENT_URI.equals(localUri)) {
            validColumnsSet = CALENDARS_TABLE_ALLOWED_LIST;
        } else if (CalendarContract.Instances.CONTENT_URI.equals(localUri)
                || CalendarContract.Instances.CONTENT_BY_DAY_URI.equals(localUri)
                || CalendarContract.Instances.CONTENT_SEARCH_URI.equals(localUri)
                || CalendarContract.Instances.CONTENT_SEARCH_BY_DAY_URI.equals(localUri)) {
            validColumnsSet = INSTANCES_TABLE_ALLOWED_LIST;
        } else {
            throw new IllegalArgumentException(String.format("Cross profile version of %d is not "
                    + "supported", localUri.toSafeString()));
        }

        if (projection != null && projection.length > 0) {
            // If there exists some columns in original projection, check if these columns are
            // allowed.
            ensureProjectionAllowed(projection, validColumnsSet);
            return projection;
        }
        // Query of content provider will return cursor that contains all columns if projection is
        // null or empty. To be consistent with this behavior, we fill projection with all allowed
        // columns if it's null or empty for cross profile Uris.
        return validColumnsSet.toArray(new String[validColumnsSet.size()]);
    }
}