15d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// Copyright 2014 The Chromium Authors. All rights reserved. 28bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 38bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)// found in the LICENSE file. 48bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 55d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)package org.chromium.chromium_linker_test_apk; 68bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 78bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)import android.app.Activity; 88bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)import android.content.Context; 98bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)import android.content.Intent; 108bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)import android.os.Bundle; 118bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)import android.util.Log; 128bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)import android.view.LayoutInflater; 138bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)import android.view.View; 148bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 15f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)import org.chromium.base.BaseSwitches; 16f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)import org.chromium.base.CommandLine; 175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import org.chromium.base.library_loader.LibraryLoader; 185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import org.chromium.base.library_loader.Linker; 195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import org.chromium.base.library_loader.ProcessInitException; 208bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)import org.chromium.content.browser.BrowserStartupController; 218bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)import org.chromium.content.browser.ContentViewClient; 22a02191e04bc25c4935f804f2c080ae28663d096dBen Murdochimport org.chromium.content.browser.ContentViewCore; 238bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)import org.chromium.content_shell.Shell; 248bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)import org.chromium.content_shell.ShellManager; 25f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)import org.chromium.ui.base.ActivityWindowAndroid; 26f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)import org.chromium.ui.base.WindowAndroid; 278bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)/** 295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Test activity used for verifying the different configuration options for the ContentLinker. 305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)public class ChromiumLinkerTestActivity extends Activity { 328bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) public static final String COMMAND_LINE_FILE = 335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) "/data/local/tmp/chromium-linker-test-command-line"; 348bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private static final String TAG = "ChromiumLinkerTestActivity"; 368bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 378bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) public static final String COMMAND_LINE_ARGS_KEY = "commandLineArgs"; 388bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 398bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) // Use this on the command-line to simulate a low-memory device, otherwise 408bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) // a regular device is simulated by this test, independently from what the 418bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) // target device running the test really is. 428bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) private static final String LOW_MEMORY_DEVICE = "--low-memory-device"; 438bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 448bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) private ShellManager mShellManager; 458bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) private WindowAndroid mWindowAndroid; 468bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 478bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) @Override 488bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) public void onCreate(final Bundle savedInstanceState) { 498bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) super.onCreate(savedInstanceState); 508bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 518bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) // Initializing the command line must occur before loading the library. 528bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) if (!CommandLine.isInitialized()) { 538bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) CommandLine.initFromFile(COMMAND_LINE_FILE); 548bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) String[] commandLineParams = getCommandLineParamsFromIntent(getIntent()); 558bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) if (commandLineParams != null) { 568bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) CommandLine.getInstance().appendSwitchesAndArguments(commandLineParams); 578bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) } 588bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) } 598bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) waitForDebuggerIfNeeded(); 608bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 618bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) // CommandLine.getInstance().hasSwitch() doesn't work here for some funky 628bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) // reason, so parse the command-line differently here: 638bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) boolean hasLowMemoryDeviceSwitch = false; 648bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) String[] cmdline = CommandLine.getJavaSwitchesOrNull(); 658bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) if (cmdline == null) 668bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) Log.i(TAG, "Command line is null"); 678bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) else { 688bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) Log.i(TAG, "Command line is:"); 698bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) for (int n = 0; n < cmdline.length; ++n) { 708bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) Log.i(TAG, " '" + cmdline[n] + "'"); 718bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) if (cmdline[n].equals(LOW_MEMORY_DEVICE)) 728bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) hasLowMemoryDeviceSwitch = true; 738bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) } 748bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) } 758bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 768bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) // Determine which kind of device to simulate from the command-line. 778bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) int memoryDeviceConfig = Linker.MEMORY_DEVICE_CONFIG_NORMAL; 788bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) if (hasLowMemoryDeviceSwitch) 798bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) memoryDeviceConfig = Linker.MEMORY_DEVICE_CONFIG_LOW; 808bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) Linker.setMemoryDeviceConfig(memoryDeviceConfig); 818bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 828bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) // Register the test runner class by name. 838bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) Linker.setTestRunnerClassName(LinkerTests.class.getName()); 848bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 858bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) // Load the library in the browser process, this will also run the test 868bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) // runner in this process. 878bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) try { 88c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch LibraryLoader.ensureInitialized(); 898bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) } catch (ProcessInitException e) { 905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) Log.i(TAG, "Cannot load chromium_linker_test:" + e); 918bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) } 928bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 938bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) // Now, start a new renderer process by creating a new view. 948bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) // This will run the test runner in the renderer process. 958bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 968bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) BrowserStartupController.get(getApplicationContext()).initChromiumBrowserProcessForTests(); 978bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 988bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) LayoutInflater inflater = 998bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); 1008bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) View view = inflater.inflate(R.layout.test_activity, null); 1018bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) mShellManager = (ShellManager) view.findViewById(R.id.shell_container); 102f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) mWindowAndroid = new ActivityWindowAndroid(this); 1038bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) mShellManager.setWindow(mWindowAndroid); 1048bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 1058bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) mShellManager.setStartupUrl("about:blank"); 1068bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 1075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) try { 1085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) BrowserStartupController.get(this).startBrowserProcessesAsync( 1095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) new BrowserStartupController.StartupCallback() { 1105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) @Override 1115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) public void onSuccess(boolean alreadyStarted) { 1125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) finishInitialization(savedInstanceState); 1135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 1145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) @Override 1165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) public void onFailure() { 1175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) initializationFailed(); 1185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 1195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) }); 1205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } catch (ProcessInitException e) { 1215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) Log.e(TAG, "Unable to load native library.", e); 1225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) finish(); 1235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 1248bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 1258bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) // TODO(digit): Ensure that after the content view is initialized, 1268bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) // the program finishes(). 1278bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) } 1288bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 1298bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) private void finishInitialization(Bundle savedInstanceState) { 1308bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) String shellUrl = ShellManager.DEFAULT_SHELL_URL; 1318bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) mShellManager.launchShell(shellUrl); 132a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch getActiveContentViewCore().setContentViewClient(new ContentViewClient()); 1338bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) } 1348bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 1358bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) private void initializationFailed() { 1368bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) Log.e(TAG, "ContentView initialization failed."); 1378bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) finish(); 1388bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) } 1398bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 1408bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) @Override 1418bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) protected void onSaveInstanceState(Bundle outState) { 1428bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) super.onSaveInstanceState(outState); 1438bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) mWindowAndroid.saveInstanceState(outState); 1448bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) } 1458bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 1468bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) private void waitForDebuggerIfNeeded() { 147f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) if (CommandLine.getInstance().hasSwitch(BaseSwitches.WAIT_FOR_JAVA_DEBUGGER)) { 1488bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) Log.e(TAG, "Waiting for Java debugger to connect..."); 1498bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) android.os.Debug.waitForDebugger(); 1508bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) Log.e(TAG, "Java debugger connected. Resuming execution."); 1518bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) } 1528bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) } 1538bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 1548bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) @Override 1558bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) protected void onStop() { 1568bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) super.onStop(); 1578bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 158a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch ContentViewCore contentViewCore = getActiveContentViewCore(); 159a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch if (contentViewCore != null) contentViewCore.onHide(); 1608bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) } 1618bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 1628bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) @Override 1638bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) protected void onStart() { 1648bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) super.onStart(); 1658bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 166a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch ContentViewCore contentViewCore = getActiveContentViewCore(); 167a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch if (contentViewCore != null) contentViewCore.onShow(); 1688bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) } 1698bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 1708bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) @Override 1718bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) public void onActivityResult(int requestCode, int resultCode, Intent data) { 1728bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) super.onActivityResult(requestCode, resultCode, data); 1738bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) mWindowAndroid.onActivityResult(requestCode, resultCode, data); 1748bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) } 1758bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 1768bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) private static String[] getCommandLineParamsFromIntent(Intent intent) { 1778bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) return intent != null ? intent.getStringArrayExtra(COMMAND_LINE_ARGS_KEY) : null; 1788bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) } 1798bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 1808bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) /** 181a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch * @return The {@link ContentViewCore} owned by the currently visible {@link Shell} or null if 182a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch * one is not showing. 1838bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) */ 184a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch public ContentViewCore getActiveContentViewCore() { 1858bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) if (mShellManager == null) 1868bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) return null; 1878bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 1888bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) Shell shell = mShellManager.getActiveShell(); 1898bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) if (shell == null) 1908bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) return null; 1918bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 192a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch return shell.getContentViewCore(); 1938bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) } 1948bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)} 195