15789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler/* 25789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler * Copyright (C) 2015 The Android Open Source Project 35789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler * 45789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler * Licensed under the Apache License, Version 2.0 (the "License"); 55789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler * you may not use this file except in compliance with the License. 65789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler * You may obtain a copy of the License at 75789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler * 85789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler * http://www.apache.org/licenses/LICENSE-2.0 95789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler * 105789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler * Unless required by applicable law or agreed to in writing, software 115789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler * distributed under the License is distributed on an "AS IS" BASIS, 125789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 135789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler * See the License for the specific language governing permissions and 145789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler * limitations under the License 155789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler */ 165789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler 175789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantlerpackage com.android.settingslib.accessibility; 185789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler 195fb3a7f6664e2009ef4931634c720284a33a693eTony Mantlerimport android.accessibilityservice.AccessibilityServiceInfo; 205789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantlerimport android.content.ComponentName; 215789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantlerimport android.content.Context; 225fb3a7f6664e2009ef4931634c720284a33a693eTony Mantlerimport android.content.pm.ResolveInfo; 23c09a02198718761e20e351f4bf0ee9e30a716d11Phil Weaverimport android.content.pm.ServiceInfo; 245789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantlerimport android.content.res.Configuration; 255789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantlerimport android.content.res.Resources; 2619df1284d9a545df044815adf57cf5c160d306fdMuyuan Liimport android.os.UserHandle; 275789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantlerimport android.provider.Settings; 285789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantlerimport android.text.TextUtils; 295fb3a7f6664e2009ef4931634c720284a33a693eTony Mantlerimport android.util.ArraySet; 305fb3a7f6664e2009ef4931634c720284a33a693eTony Mantlerimport android.view.accessibility.AccessibilityManager; 315789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler 32106fe732050f3d75a08c3bc48fdbcf84cac20b41Phil Weaverimport com.android.internal.R; 33106fe732050f3d75a08c3bc48fdbcf84cac20b41Phil Weaver 345789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantlerimport java.util.Collections; 355789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantlerimport java.util.HashSet; 365fb3a7f6664e2009ef4931634c720284a33a693eTony Mantlerimport java.util.List; 375789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantlerimport java.util.Locale; 385789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantlerimport java.util.Set; 395789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler 405789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantlerpublic class AccessibilityUtils { 415789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler public static final char ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR = ':'; 425789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler 435789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler final static TextUtils.SimpleStringSplitter sStringColonSplitter = 445789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler new TextUtils.SimpleStringSplitter(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR); 455789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler 465789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler /** 4719df1284d9a545df044815adf57cf5c160d306fdMuyuan Li * @return the set of enabled accessibility services. If there are no services, 4819df1284d9a545df044815adf57cf5c160d306fdMuyuan Li * it returns the unmodifiable {@link Collections#emptySet()}. 495789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler */ 505789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler public static Set<ComponentName> getEnabledServicesFromSettings(Context context) { 5119df1284d9a545df044815adf57cf5c160d306fdMuyuan Li return getEnabledServicesFromSettings(context, UserHandle.myUserId()); 5219df1284d9a545df044815adf57cf5c160d306fdMuyuan Li } 5319df1284d9a545df044815adf57cf5c160d306fdMuyuan Li 54c09a02198718761e20e351f4bf0ee9e30a716d11Phil Weaver public static boolean hasServiceCrashed(String packageName, String serviceName, 55c09a02198718761e20e351f4bf0ee9e30a716d11Phil Weaver List<AccessibilityServiceInfo> enabledServiceInfos) { 56c09a02198718761e20e351f4bf0ee9e30a716d11Phil Weaver for (int i = 0; i < enabledServiceInfos.size(); i++) { 57c09a02198718761e20e351f4bf0ee9e30a716d11Phil Weaver AccessibilityServiceInfo accessibilityServiceInfo = enabledServiceInfos.get(i); 58c09a02198718761e20e351f4bf0ee9e30a716d11Phil Weaver final ServiceInfo serviceInfo = enabledServiceInfos.get(i).getResolveInfo().serviceInfo; 59c09a02198718761e20e351f4bf0ee9e30a716d11Phil Weaver if (TextUtils.equals(serviceInfo.packageName, packageName) 60c09a02198718761e20e351f4bf0ee9e30a716d11Phil Weaver && TextUtils.equals(serviceInfo.name, serviceName)) { 61c09a02198718761e20e351f4bf0ee9e30a716d11Phil Weaver return accessibilityServiceInfo.crashed; 62c09a02198718761e20e351f4bf0ee9e30a716d11Phil Weaver } 63c09a02198718761e20e351f4bf0ee9e30a716d11Phil Weaver } 64c09a02198718761e20e351f4bf0ee9e30a716d11Phil Weaver return false; 65c09a02198718761e20e351f4bf0ee9e30a716d11Phil Weaver } 66c09a02198718761e20e351f4bf0ee9e30a716d11Phil Weaver 6719df1284d9a545df044815adf57cf5c160d306fdMuyuan Li /** 6819df1284d9a545df044815adf57cf5c160d306fdMuyuan Li * @return the set of enabled accessibility services for {@param userId}. If there are no 6919df1284d9a545df044815adf57cf5c160d306fdMuyuan Li * services, it returns the unmodifiable {@link Collections#emptySet()}. 7019df1284d9a545df044815adf57cf5c160d306fdMuyuan Li */ 7119df1284d9a545df044815adf57cf5c160d306fdMuyuan Li public static Set<ComponentName> getEnabledServicesFromSettings(Context context, int userId) { 7219df1284d9a545df044815adf57cf5c160d306fdMuyuan Li final String enabledServicesSetting = Settings.Secure.getStringForUser( 7319df1284d9a545df044815adf57cf5c160d306fdMuyuan Li context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, 7419df1284d9a545df044815adf57cf5c160d306fdMuyuan Li userId); 755789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler if (enabledServicesSetting == null) { 765789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler return Collections.emptySet(); 775789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler } 785789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler 795fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler final Set<ComponentName> enabledServices = new HashSet<>(); 805789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler final TextUtils.SimpleStringSplitter colonSplitter = sStringColonSplitter; 815789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler colonSplitter.setString(enabledServicesSetting); 825789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler 835789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler while (colonSplitter.hasNext()) { 845789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler final String componentNameString = colonSplitter.next(); 855789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler final ComponentName enabledService = ComponentName.unflattenFromString( 865789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler componentNameString); 875789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler if (enabledService != null) { 885789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler enabledServices.add(enabledService); 895789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler } 905789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler } 915789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler 925789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler return enabledServices; 935789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler } 945789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler 955789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler /** 965789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler * @return a localized version of the text resource specified by resId 975789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler */ 985789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler public static CharSequence getTextForLocale(Context context, Locale locale, int resId) { 995789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler final Resources res = context.getResources(); 1005789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler final Configuration config = new Configuration(res.getConfiguration()); 1015789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler config.setLocale(locale); 1025789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler final Context langContext = context.createConfigurationContext(config); 1035789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler return langContext.getText(resId); 1045789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler } 1055fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler 10619df1284d9a545df044815adf57cf5c160d306fdMuyuan Li /** 10719df1284d9a545df044815adf57cf5c160d306fdMuyuan Li * Changes an accessibility component's state. 10819df1284d9a545df044815adf57cf5c160d306fdMuyuan Li */ 1095fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler public static void setAccessibilityServiceState(Context context, ComponentName toggledService, 1105fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler boolean enabled) { 11119df1284d9a545df044815adf57cf5c160d306fdMuyuan Li setAccessibilityServiceState(context, toggledService, enabled, UserHandle.myUserId()); 11219df1284d9a545df044815adf57cf5c160d306fdMuyuan Li } 11319df1284d9a545df044815adf57cf5c160d306fdMuyuan Li 11419df1284d9a545df044815adf57cf5c160d306fdMuyuan Li /** 11519df1284d9a545df044815adf57cf5c160d306fdMuyuan Li * Changes an accessibility component's state for {@param userId}. 11619df1284d9a545df044815adf57cf5c160d306fdMuyuan Li */ 11719df1284d9a545df044815adf57cf5c160d306fdMuyuan Li public static void setAccessibilityServiceState(Context context, ComponentName toggledService, 11819df1284d9a545df044815adf57cf5c160d306fdMuyuan Li boolean enabled, int userId) { 1195fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler // Parse the enabled services. 1205fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler Set<ComponentName> enabledServices = AccessibilityUtils.getEnabledServicesFromSettings( 12119df1284d9a545df044815adf57cf5c160d306fdMuyuan Li context, userId); 1225fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler 1235fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler if (enabledServices.isEmpty()) { 1245fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler enabledServices = new ArraySet<>(1); 1255fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler } 1265fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler 1275fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler // Determine enabled services and accessibility state. 1285fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler boolean accessibilityEnabled = false; 1295fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler if (enabled) { 1305fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler enabledServices.add(toggledService); 1315fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler // Enabling at least one service enables accessibility. 1325fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler accessibilityEnabled = true; 1335fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler } else { 1345fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler enabledServices.remove(toggledService); 1355fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler // Check how many enabled and installed services are present. 1365fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler Set<ComponentName> installedServices = getInstalledServices(context); 1375fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler for (ComponentName enabledService : enabledServices) { 1385fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler if (installedServices.contains(enabledService)) { 1395fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler // Disabling the last service disables accessibility. 1405fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler accessibilityEnabled = true; 1415fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler break; 1425fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler } 1435fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler } 1445fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler } 1455fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler 1465fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler // Update the enabled services setting. 1475fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler StringBuilder enabledServicesBuilder = new StringBuilder(); 1485fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler // Keep the enabled services even if they are not installed since we 1495fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler // have no way to know whether the application restore process has 1505fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler // completed. In general the system should be responsible for the 1515fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler // clean up not settings. 1525fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler for (ComponentName enabledService : enabledServices) { 1535fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler enabledServicesBuilder.append(enabledService.flattenToString()); 1545fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler enabledServicesBuilder.append( 1555fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler AccessibilityUtils.ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR); 1565fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler } 1575fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler final int enabledServicesBuilderLength = enabledServicesBuilder.length(); 1585fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler if (enabledServicesBuilderLength > 0) { 1595fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1); 1605fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler } 16119df1284d9a545df044815adf57cf5c160d306fdMuyuan Li Settings.Secure.putStringForUser(context.getContentResolver(), 1625fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, 16319df1284d9a545df044815adf57cf5c160d306fdMuyuan Li enabledServicesBuilder.toString(), userId); 1645fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler } 1655fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler 166106fe732050f3d75a08c3bc48fdbcf84cac20b41Phil Weaver /** 167106fe732050f3d75a08c3bc48fdbcf84cac20b41Phil Weaver * Get the name of the service that should be toggled by the accessibility shortcut. Use 168106fe732050f3d75a08c3bc48fdbcf84cac20b41Phil Weaver * an OEM-configurable default if the setting has never been set. 169106fe732050f3d75a08c3bc48fdbcf84cac20b41Phil Weaver * 170106fe732050f3d75a08c3bc48fdbcf84cac20b41Phil Weaver * @param context A valid context 171106fe732050f3d75a08c3bc48fdbcf84cac20b41Phil Weaver * @param userId The user whose settings should be checked 172106fe732050f3d75a08c3bc48fdbcf84cac20b41Phil Weaver * 173106fe732050f3d75a08c3bc48fdbcf84cac20b41Phil Weaver * @return The component name, flattened to a string, of the target service. 174106fe732050f3d75a08c3bc48fdbcf84cac20b41Phil Weaver */ 175106fe732050f3d75a08c3bc48fdbcf84cac20b41Phil Weaver public static String getShortcutTargetServiceComponentNameString( 176106fe732050f3d75a08c3bc48fdbcf84cac20b41Phil Weaver Context context, int userId) { 177106fe732050f3d75a08c3bc48fdbcf84cac20b41Phil Weaver final String currentShortcutServiceId = Settings.Secure.getStringForUser( 178106fe732050f3d75a08c3bc48fdbcf84cac20b41Phil Weaver context.getContentResolver(), Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, 179106fe732050f3d75a08c3bc48fdbcf84cac20b41Phil Weaver userId); 180106fe732050f3d75a08c3bc48fdbcf84cac20b41Phil Weaver if (currentShortcutServiceId != null) { 181106fe732050f3d75a08c3bc48fdbcf84cac20b41Phil Weaver return currentShortcutServiceId; 182106fe732050f3d75a08c3bc48fdbcf84cac20b41Phil Weaver } 183106fe732050f3d75a08c3bc48fdbcf84cac20b41Phil Weaver return context.getString(R.string.config_defaultAccessibilityService); 184106fe732050f3d75a08c3bc48fdbcf84cac20b41Phil Weaver } 185106fe732050f3d75a08c3bc48fdbcf84cac20b41Phil Weaver 186ce687c5e4a0ce27e9190fb5c823ec4649fbc3712Phil Weaver /** 187ce687c5e4a0ce27e9190fb5c823ec4649fbc3712Phil Weaver * Check if the accessibility shortcut is enabled for a user 188ce687c5e4a0ce27e9190fb5c823ec4649fbc3712Phil Weaver * 189ce687c5e4a0ce27e9190fb5c823ec4649fbc3712Phil Weaver * @param context A valid context 190ce687c5e4a0ce27e9190fb5c823ec4649fbc3712Phil Weaver * @param userId The user of interest 191ce687c5e4a0ce27e9190fb5c823ec4649fbc3712Phil Weaver * @return {@code true} if the shortcut is enabled for the user. {@code false} otherwise. 192ce687c5e4a0ce27e9190fb5c823ec4649fbc3712Phil Weaver * Note that the shortcut may be enabled, but no action associated with it. 193ce687c5e4a0ce27e9190fb5c823ec4649fbc3712Phil Weaver */ 194ce687c5e4a0ce27e9190fb5c823ec4649fbc3712Phil Weaver public static boolean isShortcutEnabled(Context context, int userId) { 195ce687c5e4a0ce27e9190fb5c823ec4649fbc3712Phil Weaver return Settings.Secure.getIntForUser(context.getContentResolver(), 196ce687c5e4a0ce27e9190fb5c823ec4649fbc3712Phil Weaver Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 1, userId) == 1; 197ce687c5e4a0ce27e9190fb5c823ec4649fbc3712Phil Weaver } 198ce687c5e4a0ce27e9190fb5c823ec4649fbc3712Phil Weaver 1995fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler private static Set<ComponentName> getInstalledServices(Context context) { 2005fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler final Set<ComponentName> installedServices = new HashSet<>(); 2015fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler installedServices.clear(); 2025fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler 2035fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler final List<AccessibilityServiceInfo> installedServiceInfos = 2045fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler AccessibilityManager.getInstance(context) 2055fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler .getInstalledAccessibilityServiceList(); 2065fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler if (installedServiceInfos == null) { 2075fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler return installedServices; 2085fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler } 2095fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler 2105fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler for (final AccessibilityServiceInfo info : installedServiceInfos) { 2115fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler final ResolveInfo resolveInfo = info.getResolveInfo(); 2125fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler final ComponentName installedService = new ComponentName( 2135fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler resolveInfo.serviceInfo.packageName, 2145fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler resolveInfo.serviceInfo.name); 2155fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler installedServices.add(installedService); 2165fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler } 2175fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler return installedServices; 2185fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler } 2195fb3a7f6664e2009ef4931634c720284a33a693eTony Mantler 2205789c3e24f0963bfc85477bfbbfe93be88946024Tony Mantler} 221