1 /*
2  * Copyright (C) 2021 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.server.pm.verify.domain;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.UserIdInt;
22 import android.content.pm.IntentFilterVerificationInfo;
23 import android.content.pm.PackageManager;
24 import android.util.ArrayMap;
25 import android.util.SparseIntArray;
26 
27 import com.android.internal.annotations.GuardedBy;
28 import com.android.modules.utils.TypedXmlPullParser;
29 import com.android.modules.utils.TypedXmlSerializer;
30 import com.android.server.pm.SettingsXml;
31 
32 import org.xmlpull.v1.XmlPullParserException;
33 
34 import java.io.IOException;
35 
36 /**
37  * Reads and writes the old {@link android.content.pm.IntentFilterVerificationInfo} so that it can
38  * be migrated in to the new API. Will throw away the state once it's successfully applied so that
39  * eventually there will be no legacy state on the device.
40  *
41  * This attempt is best effort, and if the legacy state is lost that's acceptable. The user setting
42  * in the legacy API may have been set incorrectly because it was never made obvious to the user
43  * what it actually toggled, so there's a strong argument to prevent migration anyways. The user
44  * can just set their preferences again, this time with finer grained control, if the legacy state
45  * gets dropped.
46  */
47 public class DomainVerificationLegacySettings {
48 
49     public static final String TAG_DOMAIN_VERIFICATIONS_LEGACY = "domain-verifications-legacy";
50     public static final String TAG_USER_STATES = "user-states";
51     public static final String ATTR_PACKAGE_NAME = "packageName";
52     public static final String TAG_USER_STATE = "user-state";
53     public static final String ATTR_USER_ID = "userId";
54     public static final String ATTR_STATE = "state";
55 
56     @NonNull
57     private final Object mLock = new Object();
58 
59     @NonNull
60     private final ArrayMap<String, LegacyState> mStates = new ArrayMap<>();
61 
add(@onNull String packageName, @NonNull IntentFilterVerificationInfo info)62     public void add(@NonNull String packageName, @NonNull IntentFilterVerificationInfo info) {
63         synchronized (mLock) {
64             getOrCreateStateLocked(packageName).setInfo(info);
65         }
66     }
67 
add(@onNull String packageName, @UserIdInt int userId, int state)68     public void add(@NonNull String packageName, @UserIdInt int userId, int state) {
69         synchronized (mLock) {
70             getOrCreateStateLocked(packageName).addUserState(userId, state);
71         }
72     }
73 
getUserState(@onNull String packageName, @UserIdInt int userId)74     public int getUserState(@NonNull String packageName, @UserIdInt int userId) {
75         synchronized (mLock) {
76             LegacyState state = mStates.get(packageName);
77             if (state != null) {
78                 return state.getUserState(userId);
79             }
80         }
81         return PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
82     }
83 
84     @Nullable
getUserStates(@onNull String packageName)85     public SparseIntArray getUserStates(@NonNull String packageName) {
86         synchronized (mLock) {
87             LegacyState state = mStates.get(packageName);
88             if (state != null) {
89                 // Yes, this returns outside of the lock, but we assume that retrieval generally
90                 // only happens after all adding has concluded from reading settings.
91                 return state.getUserStates();
92             }
93         }
94         return null;
95     }
96 
97     @Nullable
remove(@onNull String packageName)98     public IntentFilterVerificationInfo remove(@NonNull String packageName) {
99         synchronized (mLock) {
100             LegacyState state = mStates.get(packageName);
101             if (state != null && !state.isAttached()) {
102                 state.markAttached();
103                 return state.getInfo();
104             }
105         }
106         return null;
107     }
108 
109     @GuardedBy("mLock")
110     @NonNull
getOrCreateStateLocked(@onNull String packageName)111     private LegacyState getOrCreateStateLocked(@NonNull String packageName) {
112         LegacyState state = mStates.get(packageName);
113         if (state == null) {
114             state = new LegacyState();
115             mStates.put(packageName, state);
116         }
117 
118         return state;
119     }
120 
writeSettings(TypedXmlSerializer xmlSerializer)121     public void writeSettings(TypedXmlSerializer xmlSerializer) throws IOException {
122         try (SettingsXml.Serializer serializer = SettingsXml.serializer(xmlSerializer)) {
123             try (SettingsXml.WriteSection ignored =
124                          serializer.startSection(TAG_DOMAIN_VERIFICATIONS_LEGACY)) {
125                 synchronized (mLock) {
126                     final int statesSize = mStates.size();
127                     for (int stateIndex = 0; stateIndex < statesSize; stateIndex++) {
128                         final LegacyState state = mStates.valueAt(stateIndex);
129                         final SparseIntArray userStates = state.getUserStates();
130                         if (userStates == null) {
131                             continue;
132                         }
133 
134                         final String packageName = mStates.keyAt(stateIndex);
135                         try (SettingsXml.WriteSection userStatesSection =
136                                      serializer.startSection(TAG_USER_STATES)
137                                              .attribute(ATTR_PACKAGE_NAME, packageName)) {
138                             final int userStatesSize = userStates.size();
139                             for (int userStateIndex = 0; userStateIndex < userStatesSize;
140                                     userStateIndex++) {
141                                 final int userId = userStates.keyAt(userStateIndex);
142                                 final int userState = userStates.valueAt(userStateIndex);
143                                 userStatesSection.startSection(TAG_USER_STATE)
144                                         .attribute(ATTR_USER_ID, userId)
145                                         .attribute(ATTR_STATE, userState)
146                                         .finish();
147                             }
148                         }
149                     }
150                 }
151             }
152         }
153     }
154 
readSettings(TypedXmlPullParser xmlParser)155     public void readSettings(TypedXmlPullParser xmlParser)
156             throws IOException, XmlPullParserException {
157         final SettingsXml.ChildSection child = SettingsXml.parser(xmlParser).children();
158         while (child.moveToNext()) {
159             if (TAG_USER_STATES.equals(child.getName())) {
160                 readUserStates(child);
161             }
162         }
163     }
164 
readUserStates(SettingsXml.ReadSection section)165     private void readUserStates(SettingsXml.ReadSection section) {
166         String packageName = section.getString(ATTR_PACKAGE_NAME);
167         synchronized (mLock) {
168             final LegacyState legacyState = getOrCreateStateLocked(packageName);
169             final SettingsXml.ChildSection child = section.children();
170             while (child.moveToNext()) {
171                 if (TAG_USER_STATE.equals(child.getName())) {
172                     readUserState(child, legacyState);
173                 }
174             }
175         }
176     }
177 
readUserState(SettingsXml.ReadSection section, LegacyState legacyState)178     private void readUserState(SettingsXml.ReadSection section, LegacyState legacyState) {
179         int userId = section.getInt(ATTR_USER_ID);
180         int state = section.getInt(ATTR_STATE);
181         legacyState.addUserState(userId, state);
182     }
183 
184     static class LegacyState {
185         @Nullable
186         private IntentFilterVerificationInfo mInfo;
187 
188         @Nullable
189         private SparseIntArray mUserStates;
190 
191         private boolean attached;
192 
193         @Nullable
getInfo()194         public IntentFilterVerificationInfo getInfo() {
195             return mInfo;
196         }
197 
getUserState(int userId)198         public int getUserState(int userId) {
199             return mUserStates.get(userId,
200                     PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED);
201         }
202 
203         @Nullable
getUserStates()204         public SparseIntArray getUserStates() {
205             return mUserStates;
206         }
207 
setInfo(@onNull IntentFilterVerificationInfo info)208         public void setInfo(@NonNull IntentFilterVerificationInfo info) {
209             mInfo = info;
210         }
211 
addUserState(@serIdInt int userId, int state)212         public void addUserState(@UserIdInt int userId, int state) {
213             if (mUserStates == null) {
214                 mUserStates = new SparseIntArray(1);
215             }
216             mUserStates.put(userId, state);
217         }
218 
isAttached()219         public boolean isAttached() {
220             return attached;
221         }
222 
markAttached()223         public void markAttached() {
224             attached = true;
225         }
226     }
227 }
228