1554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe/* 2554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * Copyright (C) 2015 The Android Open Source Project 3554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * 4554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * Licensed under the Apache License, Version 2.0 (the "License"); 5554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * you may not use this file except in compliance with the License. 6554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * You may obtain a copy of the License at 7554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * 8554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * http://www.apache.org/licenses/LICENSE-2.0 9554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * 10554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * Unless required by applicable law or agreed to in writing, software 11554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * distributed under the License is distributed on an "AS IS" BASIS, 12554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * See the License for the specific language governing permissions and 14554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * limitations under the License. 15554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe */ 16554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 17554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampepackage com.android.preload; 18554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 19554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampeimport com.android.ddmlib.AndroidDebugBridge; 20554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampeimport com.android.ddmlib.AndroidDebugBridge.IClientChangeListener; 21554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampeimport com.android.ddmlib.Client; 22554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampeimport com.android.ddmlib.IDevice; 23554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 24554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe/** 25554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * Helper class for common communication with a Client (the ddms name for a running application). 26554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * 27554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * Instances take a default timeout parameter that's applied to all functions without explicit 28554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * timeout. Timeouts are in milliseconds. 29554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe */ 30554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampepublic class ClientUtils { 31554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 32554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe private int defaultTimeout; 33554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 34554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe public ClientUtils() { 35554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe this(10000); 36554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 37554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 38554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe public ClientUtils(int defaultTimeout) { 39554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe this.defaultTimeout = defaultTimeout; 40554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 41554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 42554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe /** 43554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * Shortcut for findClient with default timeout. 44554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe */ 45554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe public Client findClient(IDevice device, String processName, int processPid) { 46554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe return findClient(device, processName, processPid, defaultTimeout); 47554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 48554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 49554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe /** 50554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * Find the client with the given process name or process id. The name takes precedence over 51554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * the process id (if valid). Stop looking after the given timeout. 52554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * 53554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * @param device The device to communicate with. 54554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * @param processName The name of the process. May be null. 55554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * @param processPid The pid of the process. Values less than or equal to zero are ignored. 56554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * @param timeout The amount of milliseconds to wait, at most. 57554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * @return The client, if found. Otherwise null. 58554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe */ 59554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe public Client findClient(IDevice device, String processName, int processPid, int timeout) { 60554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe WaitForClient wfc = new WaitForClient(device, processName, processPid, timeout); 61554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe return wfc.get(); 62554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 63554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 64554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe /** 65554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * Shortcut for findAllClients with default timeout. 66554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe */ 67554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe public Client[] findAllClients(IDevice device) { 68554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe return findAllClients(device, defaultTimeout); 69554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 70554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 71554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe /** 72554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * Retrieve all clients known to the given device. Wait at most the given timeout. 73554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * 74554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * @param device The device to investigate. 75554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * @param timeout The amount of milliseconds to wait, at most. 76554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * @return An array of clients running on the given device. May be null depending on the 77554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe * device implementation. 78554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe */ 79554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe public Client[] findAllClients(IDevice device, int timeout) { 80554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe if (device.hasClients()) { 81554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe return device.getClients(); 82554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 83554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe WaitForClients wfc = new WaitForClients(device, timeout); 84554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe return wfc.get(); 85554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 86554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 87554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe private static class WaitForClient implements IClientChangeListener { 88554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 89554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe private IDevice device; 90554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe private String processName; 91554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe private int processPid; 92554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe private long timeout; 93554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe private Client result; 94554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 95554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe public WaitForClient(IDevice device, String processName, int processPid, long timeout) { 96554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe this.device = device; 97554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe this.processName = processName; 98554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe this.processPid = processPid; 99554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe this.timeout = timeout; 100554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe this.result = null; 101554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 102554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 103554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe public Client get() { 104554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe synchronized (this) { 105554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe AndroidDebugBridge.addClientChangeListener(this); 106554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 107554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe // Maybe it's already there. 108554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe if (result == null) { 109554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe result = searchForClient(device); 110554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 111554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 112554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe if (result == null) { 113554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe try { 114554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe wait(timeout); 115554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } catch (InterruptedException e) { 116554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe // Note: doesn't guard for spurious wakeup. 117554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 118554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 119554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 120554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 121554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe AndroidDebugBridge.removeClientChangeListener(this); 122554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe return result; 123554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 124554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 125554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe private Client searchForClient(IDevice device) { 126554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe if (processName != null) { 127554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe Client tmp = device.getClient(processName); 128554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe if (tmp != null) { 129554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe return tmp; 130554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 131554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 132554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe if (processPid > 0) { 133554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe String name = device.getClientName(processPid); 134554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe if (name != null && !name.isEmpty()) { 135554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe Client tmp = device.getClient(name); 136554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe if (tmp != null) { 137554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe return tmp; 138554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 139554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 140554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 141554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe if (processPid > 0) { 142554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe // Try manual search. 143554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe for (Client cl : device.getClients()) { 144554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe if (cl.getClientData().getPid() == processPid 145554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe && cl.getClientData().getClientDescription() != null) { 146554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe return cl; 147554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 148554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 149554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 150554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe return null; 151554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 152554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 153554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe private boolean isTargetClient(Client c) { 154554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe if (processPid > 0 && c.getClientData().getPid() == processPid) { 155554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe return true; 156554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 157554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe if (processName != null 158554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe && processName.equals(c.getClientData().getClientDescription())) { 159554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe return true; 160554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 161554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe return false; 162554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 163554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 164554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe @Override 165554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe public void clientChanged(Client arg0, int arg1) { 166554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe synchronized (this) { 167554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe if ((arg1 & Client.CHANGE_INFO) != 0 && (arg0.getDevice() == device)) { 168554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe if (isTargetClient(arg0)) { 169554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe result = arg0; 170554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe notifyAll(); 171554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 172554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 173554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 174554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 175554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 176554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 177554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe private static class WaitForClients implements IClientChangeListener { 178554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 179554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe private IDevice device; 180554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe private long timeout; 181554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 182554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe public WaitForClients(IDevice device, long timeout) { 183554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe this.device = device; 184554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe this.timeout = timeout; 185554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 186554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 187554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe public Client[] get() { 188554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe synchronized (this) { 189554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe AndroidDebugBridge.addClientChangeListener(this); 190554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 191554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe if (device.hasClients()) { 192554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe return device.getClients(); 193554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 194554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 195554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe try { 196554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe wait(timeout); // Note: doesn't guard for spurious wakeup. 197554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } catch (InterruptedException exc) { 198554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 199554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 200554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe // We will be woken up when the first client data arrives. Sleep a little longer 201554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe // to give (hopefully all of) the rest of the clients a chance to become available. 202554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe // Note: a loop with timeout is brittle as well and complicated, just accept this 203554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe // for now. 204554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe try { 205554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe Thread.sleep(500); 206554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } catch (InterruptedException exc) { 207554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 208554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 209554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 210554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe AndroidDebugBridge.removeClientChangeListener(this); 211554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 212554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe return device.getClients(); 213554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 214554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe 215554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe @Override 216554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe public void clientChanged(Client arg0, int arg1) { 217554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe synchronized (this) { 218554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe if ((arg1 & Client.CHANGE_INFO) != 0 && (arg0.getDevice() == device)) { 219554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe notifyAll(); 220554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 221554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 222554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 223554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe } 224554d7ee0f5d177b6c0bce805f5a5917b6b211978Andreas Gampe} 225