17db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov/* 27db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov * Copyright (C) 2015 The Android Open Source Project 37db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov * 47db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov * Licensed under the Apache License, Version 2.0 (the "License"); 57db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov * you may not use this file except in compliance with the License. 67db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov * You may obtain a copy of the License at 77db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov * 87db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov * http://www.apache.org/licenses/LICENSE-2.0 97db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov * 107db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov * Unless required by applicable law or agreed to in writing, software 117db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov * distributed under the License is distributed on an "AS IS" BASIS, 127db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 137db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov * See the License for the specific language governing permissions and 147db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov * limitations under the License 157db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov */ 167db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov 177db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolovpackage android.content.pm; 187db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov 197db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolovimport android.Manifest; 207db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolovimport android.app.AppGlobals; 217db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolovimport android.content.Intent; 227db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolovimport android.os.RemoteException; 237db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolovimport android.os.UserHandle; 247db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolovimport android.util.ArraySet; 251682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolovimport android.view.inputmethod.InputMethod; 267db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov 277db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolovimport com.android.internal.annotations.VisibleForTesting; 287db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov 297db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolovimport java.util.ArrayList; 307db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolovimport java.util.List; 317db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov 327db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov/** 337db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov * Helper class for querying installed applications using multiple criteria. 347db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov * 357db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov * @hide 367db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov */ 377db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolovpublic class AppsQueryHelper { 387db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov 397db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov /** 407db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov * Return apps without launcher icon 417db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov */ 427db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov public static int GET_NON_LAUNCHABLE_APPS = 1; 437db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov 447db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov /** 451682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov * Return apps with {@link Manifest.permission#INTERACT_ACROSS_USERS} permission 467db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov */ 477db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov public static int GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM = 1 << 1; 487db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov 49b3b4f61525a3547920d642841202730f3cb16151Fyodor Kupolov /** 501682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov * Return all input methods available for the current user. 51b3b4f61525a3547920d642841202730f3cb16151Fyodor Kupolov */ 521682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov public static int GET_IMES = 1 << 2; 53b3b4f61525a3547920d642841202730f3cb16151Fyodor Kupolov 54bdbc9692c7cb365d9d3f239baa2377724a6f7bc8Fyodor Kupolov /** 55bdbc9692c7cb365d9d3f239baa2377724a6f7bc8Fyodor Kupolov * Return all apps that are flagged as required for the system user. 56bdbc9692c7cb365d9d3f239baa2377724a6f7bc8Fyodor Kupolov */ 57bdbc9692c7cb365d9d3f239baa2377724a6f7bc8Fyodor Kupolov public static int GET_REQUIRED_FOR_SYSTEM_USER = 1 << 3; 58bdbc9692c7cb365d9d3f239baa2377724a6f7bc8Fyodor Kupolov 591682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov private final IPackageManager mPackageManager; 607db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov private List<ApplicationInfo> mAllApps; 617db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov 621682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov public AppsQueryHelper(IPackageManager packageManager) { 631682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov mPackageManager = packageManager; 641682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov } 651682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov 661682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov public AppsQueryHelper() { 671682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov this(AppGlobals.getPackageManager()); 687db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov } 697db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov 707db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov /** 717db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov * Return a List of all packages that satisfy a specified criteria. 727db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov * @param flags search flags. Use any combination of {@link #GET_NON_LAUNCHABLE_APPS}, 731682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov * {@link #GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM} or {@link #GET_IMES}. 747db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov * @param systemAppsOnly if true, only system apps will be returned 757db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov * @param user user, whose apps are queried 767db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov */ 777db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov public List<String> queryApps(int flags, boolean systemAppsOnly, UserHandle user) { 787db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov boolean nonLaunchableApps = (flags & GET_NON_LAUNCHABLE_APPS) > 0; 797db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov boolean interactAcrossUsers = (flags & GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM) > 0; 801682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov boolean imes = (flags & GET_IMES) > 0; 81bdbc9692c7cb365d9d3f239baa2377724a6f7bc8Fyodor Kupolov boolean requiredForSystemUser = (flags & GET_REQUIRED_FOR_SYSTEM_USER) > 0; 827db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov if (mAllApps == null) { 837db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov mAllApps = getAllApps(user.getIdentifier()); 847db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov } 857db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov 867db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov List<String> result = new ArrayList<>(); 877db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov if (flags == 0) { 887db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov final int allAppsSize = mAllApps.size(); 897db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov for (int i = 0; i < allAppsSize; i++) { 907db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov final ApplicationInfo appInfo = mAllApps.get(i); 917db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov if (systemAppsOnly && !appInfo.isSystemApp()) { 927db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov continue; 937db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov } 947db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov result.add(appInfo.packageName); 957db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov } 967db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov return result; 977db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov } 987db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov 997db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov if (nonLaunchableApps) { 1007db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER); 1017db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov final List<ResolveInfo> resolveInfos = queryIntentActivitiesAsUser(intent, 1027db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov user.getIdentifier()); 1037db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov 1047db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov ArraySet<String> appsWithLaunchers = new ArraySet<>(); 1057db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov final int resolveInfosSize = resolveInfos.size(); 1067db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov for (int i = 0; i < resolveInfosSize; i++) { 1077db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov appsWithLaunchers.add(resolveInfos.get(i).activityInfo.packageName); 1087db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov } 1097db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov final int allAppsSize = mAllApps.size(); 1107db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov for (int i = 0; i < allAppsSize; i++) { 1117db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov final ApplicationInfo appInfo = mAllApps.get(i); 1127db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov if (systemAppsOnly && !appInfo.isSystemApp()) { 1137db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov continue; 1147db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov } 1157db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov final String packageName = appInfo.packageName; 1167db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov if (!appsWithLaunchers.contains(packageName)) { 1177db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov result.add(packageName); 1187db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov } 1197db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov } 1207db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov } 1217db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov if (interactAcrossUsers) { 1227db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov final List<PackageInfo> packagesHoldingPermissions = getPackagesHoldingPermission( 1237db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov Manifest.permission.INTERACT_ACROSS_USERS, user.getIdentifier()); 1247db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov final int packagesHoldingPermissionsSize = packagesHoldingPermissions.size(); 1257db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov for (int i = 0; i < packagesHoldingPermissionsSize; i++) { 1267db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov PackageInfo packageInfo = packagesHoldingPermissions.get(i); 1277db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov if (systemAppsOnly && !packageInfo.applicationInfo.isSystemApp()) { 1287db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov continue; 1297db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov } 1307db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov if (!result.contains(packageInfo.packageName)) { 1317db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov result.add(packageInfo.packageName); 1327db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov } 1337db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov } 1347db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov } 1357db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov 1361682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov if (imes) { 1371682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov final List<ResolveInfo> resolveInfos = queryIntentServicesAsUser( 1381682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov new Intent(InputMethod.SERVICE_INTERFACE), user.getIdentifier()); 1391682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov final int resolveInfosSize = resolveInfos.size(); 1401682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov 1411682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov for (int i = 0; i < resolveInfosSize; i++) { 1421682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov ServiceInfo serviceInfo = resolveInfos.get(i).serviceInfo; 1431682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov if (systemAppsOnly && !serviceInfo.applicationInfo.isSystemApp()) { 144b3b4f61525a3547920d642841202730f3cb16151Fyodor Kupolov continue; 145b3b4f61525a3547920d642841202730f3cb16151Fyodor Kupolov } 1461682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov if (!result.contains(serviceInfo.packageName)) { 1471682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov result.add(serviceInfo.packageName); 148b3b4f61525a3547920d642841202730f3cb16151Fyodor Kupolov } 149b3b4f61525a3547920d642841202730f3cb16151Fyodor Kupolov } 150b3b4f61525a3547920d642841202730f3cb16151Fyodor Kupolov } 151b3b4f61525a3547920d642841202730f3cb16151Fyodor Kupolov 152bdbc9692c7cb365d9d3f239baa2377724a6f7bc8Fyodor Kupolov if (requiredForSystemUser) { 153bdbc9692c7cb365d9d3f239baa2377724a6f7bc8Fyodor Kupolov final int allAppsSize = mAllApps.size(); 154bdbc9692c7cb365d9d3f239baa2377724a6f7bc8Fyodor Kupolov for (int i = 0; i < allAppsSize; i++) { 155bdbc9692c7cb365d9d3f239baa2377724a6f7bc8Fyodor Kupolov final ApplicationInfo appInfo = mAllApps.get(i); 156bdbc9692c7cb365d9d3f239baa2377724a6f7bc8Fyodor Kupolov if (systemAppsOnly && !appInfo.isSystemApp()) { 157bdbc9692c7cb365d9d3f239baa2377724a6f7bc8Fyodor Kupolov continue; 158bdbc9692c7cb365d9d3f239baa2377724a6f7bc8Fyodor Kupolov } 159bdbc9692c7cb365d9d3f239baa2377724a6f7bc8Fyodor Kupolov if (appInfo.isRequiredForSystemUser()) { 160bdbc9692c7cb365d9d3f239baa2377724a6f7bc8Fyodor Kupolov result.add(appInfo.packageName); 161bdbc9692c7cb365d9d3f239baa2377724a6f7bc8Fyodor Kupolov } 162bdbc9692c7cb365d9d3f239baa2377724a6f7bc8Fyodor Kupolov } 163bdbc9692c7cb365d9d3f239baa2377724a6f7bc8Fyodor Kupolov } 1647db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov return result; 1657db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov } 1667db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov 1677db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov @VisibleForTesting 1687db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov @SuppressWarnings("unchecked") 1697db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov protected List<ApplicationInfo> getAllApps(int userId) { 1707db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov try { 1711682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov return mPackageManager.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES 1721682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov | PackageManager.GET_DISABLED_COMPONENTS, userId).getList(); 1737db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov } catch (RemoteException e) { 174d5896630f6a2f21da107031cab216dc93bdcd851Jeff Sharkey throw e.rethrowFromSystemServer(); 1757db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov } 1767db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov } 1777db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov 1787db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov @VisibleForTesting 1797db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov protected List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int userId) { 1801682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov try { 1811682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov return mPackageManager.queryIntentActivities(intent, null, 1821682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov PackageManager.GET_DISABLED_COMPONENTS 1831682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov | PackageManager.GET_UNINSTALLED_PACKAGES, 184d5896630f6a2f21da107031cab216dc93bdcd851Jeff Sharkey userId).getList(); 1851682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov } catch (RemoteException e) { 186d5896630f6a2f21da107031cab216dc93bdcd851Jeff Sharkey throw e.rethrowFromSystemServer(); 1871682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov } 1887db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov } 1897db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov 1907db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov @VisibleForTesting 1911682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov protected List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int userId) { 1927db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov try { 1931682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov return mPackageManager.queryIntentServices(intent, null, 1941682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov PackageManager.GET_META_DATA 195d5896630f6a2f21da107031cab216dc93bdcd851Jeff Sharkey | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, userId).getList(); 1967db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov } catch (RemoteException e) { 197d5896630f6a2f21da107031cab216dc93bdcd851Jeff Sharkey throw e.rethrowFromSystemServer(); 1987db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov } 1997db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov } 200b3b4f61525a3547920d642841202730f3cb16151Fyodor Kupolov 201b3b4f61525a3547920d642841202730f3cb16151Fyodor Kupolov @VisibleForTesting 202b3b4f61525a3547920d642841202730f3cb16151Fyodor Kupolov @SuppressWarnings("unchecked") 2031682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov protected List<PackageInfo> getPackagesHoldingPermission(String perm, int userId) { 2041682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov try { 2051682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov return mPackageManager.getPackagesHoldingPermissions(new String[]{perm}, 0, 2061682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov userId).getList(); 2071682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov } catch (RemoteException e) { 208d5896630f6a2f21da107031cab216dc93bdcd851Jeff Sharkey throw e.rethrowFromSystemServer(); 2091682dad7ed303fc43a07e70d0bb5cb42103a7624Fyodor Kupolov } 210b3b4f61525a3547920d642841202730f3cb16151Fyodor Kupolov } 2117db5af124e551554f7a2f1abdd2a486c937910feFyodor Kupolov} 212