1 /*
2  * Copyright (C) 2022 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.permission.safetylabel;
18 
19 import android.os.PersistableBundle;
20 
21 import androidx.annotation.NonNull;
22 import androidx.annotation.Nullable;
23 import androidx.annotation.VisibleForTesting;
24 
25 import com.android.permission.safetylabel.DataPurposeConstants.Purpose;
26 
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.Map;
31 import java.util.Set;
32 
33 /**
34  * Data usage type representation. Types are specific to a {@link DataCategory} and contains
35  * metadata related to the data usage purpose.
36  */
37 public class DataType {
38     @VisibleForTesting static final String KEY_PURPOSES = "purposes";
39     @VisibleForTesting static final String KEY_IS_COLLECTION_OPTIONAL = "is_collection_optional";
40     @VisibleForTesting static final String KEY_EPHEMERAL = "ephemeral";
41 
42     @Purpose private final Set<Integer> mPurposeSet;
43     private final Boolean mIsCollectionOptional;
44     private final Boolean mEphemeral;
45 
DataType( @onNull @urpose Set<Integer> purposeSet, @Nullable Boolean isCollectionOptional, @Nullable Boolean ephemeral)46     private DataType(
47             @NonNull @Purpose Set<Integer> purposeSet,
48             @Nullable Boolean isCollectionOptional,
49             @Nullable Boolean ephemeral) {
50         this.mPurposeSet = purposeSet;
51         this.mIsCollectionOptional = isCollectionOptional;
52         this.mEphemeral = ephemeral;
53     }
54 
55     /**
56      * Returns a {@link java.util.Collections.UnmodifiableMap} of String type key to {@link
57      * DataType} created by parsing a {@link PersistableBundle}
58      */
59     @NonNull
60     @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
getDataTypeMap( @ullable PersistableBundle dataCategoryBundle, @NonNull String dataUsage, @NonNull String category)61     static Map<String, DataType> getDataTypeMap(
62             @Nullable PersistableBundle dataCategoryBundle,
63             @NonNull String dataUsage,
64             @NonNull String category) {
65         if (dataCategoryBundle == null || dataCategoryBundle.isEmpty()) {
66             return Collections.emptyMap();
67         }
68 
69         Map<String, DataType> dataTypeMap = new HashMap<>();
70         Set<String> validDataTypesForCategory =
71                 DataTypeConstants.getValidDataTypesForCategory(category);
72         for (String type : validDataTypesForCategory) {
73             PersistableBundle dataTypeBundle = dataCategoryBundle.getPersistableBundle(type);
74             DataType dataType = getDataType(dataTypeBundle, dataUsage);
75             if (dataType != null) {
76                 dataTypeMap.put(type, dataType);
77             }
78         }
79         return Collections.unmodifiableMap(dataTypeMap);
80     }
81 
82     /**
83      * Returns {@link DataType} created by parsing the {@link android.os.PersistableBundle}
84      * representation of DataType
85      */
86     @Nullable
87     @VisibleForTesting
getDataType( @ullable PersistableBundle dataTypeBundle, @NonNull String dataUsage)88     static DataType getDataType(
89             @Nullable PersistableBundle dataTypeBundle, @NonNull String dataUsage) {
90         if (dataTypeBundle == null || dataTypeBundle.isEmpty()) {
91             return null;
92         }
93 
94         // purposes are required, if not present, treat as invalid
95         int[] purposeList = dataTypeBundle.getIntArray(KEY_PURPOSES);
96         if (purposeList == null || purposeList.length == 0) {
97             return null;
98         }
99 
100         // Filter to set of valid purposes, and return invalid if empty
101         Set<Integer> purposeSet = new HashSet<>();
102         for (int purpose : purposeList) {
103             if (DataPurposeConstants.getValidPurposes().contains(purpose)) {
104                 purposeSet.add(purpose);
105             }
106         }
107         if (purposeSet.isEmpty()) {
108             return null;
109         }
110 
111         // Only set/expected for DATA COLLECTED. DATA SHARED should be null
112         Boolean isCollectionOptional = null;
113         Boolean ephemeral = null;
114         if (DataLabelConstants.DATA_USAGE_COLLECTED.equals(dataUsage)) {
115             isCollectionOptional =
116                     dataTypeBundle.containsKey(KEY_IS_COLLECTION_OPTIONAL)
117                             ? dataTypeBundle.getBoolean(KEY_IS_COLLECTION_OPTIONAL)
118                             : null;
119             ephemeral =
120                     dataTypeBundle.containsKey(KEY_EPHEMERAL)
121                             ? dataTypeBundle.getBoolean(KEY_EPHEMERAL)
122                             : null;
123         }
124 
125         return new DataType(purposeSet, isCollectionOptional, ephemeral);
126     }
127 
128     /**
129      * Returns {@link Set} of valid {@link Integer} purposes for using the associated data category
130      * and type
131      */
132     @NonNull
getPurposeSet()133     public Set<Integer> getPurposeSet() {
134         return mPurposeSet;
135     }
136 
137     /**
138      * For data-collected, returns {@code true} if data usage is user optional and {@code false} if
139      * data usage is required. Should return {@code null} for data-shared.
140      */
141     @Nullable
getIsCollectionOptional()142     public Boolean getIsCollectionOptional() {
143         return mIsCollectionOptional;
144     }
145 
146     /**
147      * For data-collected, returns {@code true} if data usage is user optional and {@code false} if
148      * data usage is processed ephemerally. Should return {@code null} for data-shared.
149      */
150     @Nullable
getEphemeral()151     public Boolean getEphemeral() {
152         return mEphemeral;
153     }
154 }
155