blob: 19fd93fee5f03f29752f705db90328621f291e45 [file] [log] [blame]
Jorim Jaggi7b614372016-09-28 15:17:50 +02001/*
Wale Ogunwale59507092018-10-29 09:00:30 -07002 * Copyright (C) 2018 The Android Open Source Project
Jorim Jaggi7b614372016-09-28 15:17:50 +02003 *
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
Wale Ogunwale59507092018-10-29 09:00:30 -070014 * limitations under the License
Jorim Jaggi7b614372016-09-28 15:17:50 +020015 */
16
Wale Ogunwale59507092018-10-29 09:00:30 -070017package com.android.server.wm;
Jorim Jaggi7b614372016-09-28 15:17:50 +020018
Issei Suzukicac2a502019-04-16 16:52:50 +020019import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
20
Tadashi G. Takaoka74ccec22018-10-23 11:07:13 +090021import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
Brett Chabota26eda92018-07-23 13:08:30 -070022
Yorke Leebd54c2a2016-10-25 13:49:23 -070023import static org.junit.Assert.assertEquals;
24import static org.junit.Assert.assertFalse;
25import static org.junit.Assert.assertTrue;
Jorim Jaggi7b614372016-09-28 15:17:50 +020026
27import android.app.Activity;
Sudheer Shankadc589ac2016-11-10 15:30:17 -080028import android.app.ActivityManager;
Yorke Leebd54c2a2016-10-25 13:49:23 -070029import android.app.ActivityManager.TaskDescription;
Wale Ogunwale04d9cb52018-04-30 13:55:07 -070030import android.app.ActivityTaskManager;
Issei Suzukicac2a502019-04-16 16:52:50 +020031import android.app.ActivityView;
Jorim Jaggi7b614372016-09-28 15:17:50 +020032import android.app.IActivityManager;
33import android.app.ITaskStackListener;
Issei Suzukicac2a502019-04-16 16:52:50 +020034import android.app.Instrumentation;
Yorke Leebd54c2a2016-10-25 13:49:23 -070035import android.app.Instrumentation.ActivityMonitor;
36import android.app.TaskStackListener;
37import android.content.ComponentName;
Jorim Jaggi7b614372016-09-28 15:17:50 +020038import android.content.Context;
39import android.content.Intent;
Yorke Leebd54c2a2016-10-25 13:49:23 -070040import android.content.pm.ActivityInfo;
Issei Suzukicac2a502019-04-16 16:52:50 +020041import android.os.Bundle;
Jorim Jaggi7b614372016-09-28 15:17:50 +020042import android.os.RemoteException;
Mark Renouf0d4bc922019-02-08 17:01:58 -050043import android.platform.test.annotations.Presubmit;
Jorim Jaggi7b614372016-09-28 15:17:50 +020044import android.support.test.uiautomator.UiDevice;
Yorke Leebd54c2a2016-10-25 13:49:23 -070045import android.text.TextUtils;
Issei Suzukicac2a502019-04-16 16:52:50 +020046import android.view.ViewGroup;
Brett Chabota26eda92018-07-23 13:08:30 -070047
Yunfan Chenc1caf6b2019-04-12 13:58:51 +090048import androidx.test.filters.FlakyTest;
Brett Chabota26eda92018-07-23 13:08:30 -070049import androidx.test.filters.MediumTest;
Brett Chabota26eda92018-07-23 13:08:30 -070050
Jorim Jaggi7b614372016-09-28 15:17:50 +020051import com.android.internal.annotations.GuardedBy;
Brett Chabota26eda92018-07-23 13:08:30 -070052
Yorke Leebd54c2a2016-10-25 13:49:23 -070053import org.junit.After;
Jorim Jaggi7b614372016-09-28 15:17:50 +020054import org.junit.Before;
55import org.junit.Test;
Jorim Jaggi7b614372016-09-28 15:17:50 +020056
Brett Chabota26eda92018-07-23 13:08:30 -070057import java.util.concurrent.CountDownLatch;
58import java.util.concurrent.TimeUnit;
59
Tadashi G. Takaoka74ccec22018-10-23 11:07:13 +090060/**
61 * Build/Install/Run:
62 * atest WmTests:TaskStackChangedListenerTest
63 */
Jorim Jaggi7b614372016-09-28 15:17:50 +020064@MediumTest
Yorke Leebd54c2a2016-10-25 13:49:23 -070065public class TaskStackChangedListenerTest {
Jorim Jaggi7b614372016-09-28 15:17:50 +020066
67 private IActivityManager mService;
Yorke Leebd54c2a2016-10-25 13:49:23 -070068 private ITaskStackListener mTaskStackListener;
Jorim Jaggi7b614372016-09-28 15:17:50 +020069
70 private static final Object sLock = new Object();
71 @GuardedBy("sLock")
72 private static boolean sTaskStackChangedCalled;
73 private static boolean sActivityBResumed;
74
75 @Before
76 public void setUp() throws Exception {
Sudheer Shankadc589ac2016-11-10 15:30:17 -080077 mService = ActivityManager.getService();
Yorke Leebd54c2a2016-10-25 13:49:23 -070078 }
79
80 @After
81 public void tearDown() throws Exception {
Wale Ogunwale04d9cb52018-04-30 13:55:07 -070082 ActivityTaskManager.getService().unregisterTaskStackListener(mTaskStackListener);
Yorke Leebd54c2a2016-10-25 13:49:23 -070083 mTaskStackListener = null;
Jorim Jaggi7b614372016-09-28 15:17:50 +020084 }
85
86 @Test
Mark Renouf0d4bc922019-02-08 17:01:58 -050087 @Presubmit
Yunfan Chenc1caf6b2019-04-12 13:58:51 +090088 @FlakyTest(bugId = 130388819)
Jorim Jaggi7b614372016-09-28 15:17:50 +020089 public void testTaskStackChanged_afterFinish() throws Exception {
Yorke Leebd54c2a2016-10-25 13:49:23 -070090 registerTaskStackChangedListener(new TaskStackListener() {
91 @Override
92 public void onTaskStackChanged() throws RemoteException {
93 synchronized (sLock) {
94 sTaskStackChangedCalled = true;
95 }
96 }
97 });
98
Tadashi G. Takaoka74ccec22018-10-23 11:07:13 +090099 Context context = getInstrumentation().getContext();
Mark Renouf0d4bc922019-02-08 17:01:58 -0500100 context.startActivity(
101 new Intent(context, ActivityA.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
Jorim Jaggi7b614372016-09-28 15:17:50 +0200102 UiDevice.getInstance(getInstrumentation()).waitForIdle();
103 synchronized (sLock) {
Tadashi G. Takaoka74ccec22018-10-23 11:07:13 +0900104 assertTrue(sTaskStackChangedCalled);
Jorim Jaggi7b614372016-09-28 15:17:50 +0200105 }
Tadashi G. Takaoka74ccec22018-10-23 11:07:13 +0900106 assertTrue(sActivityBResumed);
Jorim Jaggi7b614372016-09-28 15:17:50 +0200107 }
108
Yorke Leebd54c2a2016-10-25 13:49:23 -0700109 @Test
Yunfan Chen6a938aa2019-03-13 13:59:07 -0700110 @Presubmit
Yorke Leebd54c2a2016-10-25 13:49:23 -0700111 public void testTaskDescriptionChanged() throws Exception {
112 final Object[] params = new Object[2];
113 final CountDownLatch latch = new CountDownLatch(1);
114 registerTaskStackChangedListener(new TaskStackListener() {
Tadashi G. Takaoka74ccec22018-10-23 11:07:13 +0900115 int mTaskId = -1;
Yorke Leebd54c2a2016-10-25 13:49:23 -0700116
117 @Override
118 public void onTaskCreated(int taskId, ComponentName componentName)
119 throws RemoteException {
Tadashi G. Takaoka74ccec22018-10-23 11:07:13 +0900120 mTaskId = taskId;
Yorke Leebd54c2a2016-10-25 13:49:23 -0700121 }
122 @Override
123 public void onTaskDescriptionChanged(int taskId, TaskDescription td)
124 throws RemoteException {
Tadashi G. Takaoka74ccec22018-10-23 11:07:13 +0900125 if (mTaskId == taskId && !TextUtils.isEmpty(td.getLabel())) {
Yorke Leebd54c2a2016-10-25 13:49:23 -0700126 params[0] = taskId;
127 params[1] = td;
128 latch.countDown();
129 }
130 }
131 });
Yunfan Chen968cc7f2019-02-15 20:14:14 +0900132
133 int taskId;
134 synchronized (sLock) {
135 taskId = startTestActivity(ActivityTaskDescriptionChange.class).getTaskId();
136 }
Yorke Leebd54c2a2016-10-25 13:49:23 -0700137 waitForCallback(latch);
Yunfan Chen968cc7f2019-02-15 20:14:14 +0900138 assertEquals(taskId, params[0]);
Yorke Leebd54c2a2016-10-25 13:49:23 -0700139 assertEquals("Test Label", ((TaskDescription) params[1]).getLabel());
140 }
141
142 @Test
Yunfan Chen6a938aa2019-03-13 13:59:07 -0700143 @Presubmit
Yorke Leebd54c2a2016-10-25 13:49:23 -0700144 public void testActivityRequestedOrientationChanged() throws Exception {
145 final int[] params = new int[2];
146 final CountDownLatch latch = new CountDownLatch(1);
147 registerTaskStackChangedListener(new TaskStackListener() {
148 @Override
149 public void onActivityRequestedOrientationChanged(int taskId,
150 int requestedOrientation) {
151 params[0] = taskId;
152 params[1] = requestedOrientation;
153 latch.countDown();
154 }
155 });
Yunfan Chen968cc7f2019-02-15 20:14:14 +0900156 int taskId;
157 synchronized (sLock) {
158 taskId = startTestActivity(ActivityRequestedOrientationChange.class).getTaskId();
159 }
Yorke Leebd54c2a2016-10-25 13:49:23 -0700160 waitForCallback(latch);
Yunfan Chen968cc7f2019-02-15 20:14:14 +0900161 assertEquals(taskId, params[0]);
Yorke Leebd54c2a2016-10-25 13:49:23 -0700162 assertEquals(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, params[1]);
163 }
164
Yorke Leebd54c2a2016-10-25 13:49:23 -0700165 /**
166 * Tests for onTaskCreated, onTaskMovedToFront, onTaskRemoved and onTaskRemovalStarted.
167 */
Kazuki Takise9a0be722018-08-08 14:44:15 +0900168 @Test
Yunfan Chenccaa38f2019-04-05 14:03:59 +0900169 @Presubmit
Yunfan Chenc1caf6b2019-04-12 13:58:51 +0900170 @FlakyTest(bugId = 130388819)
Yorke Leebd54c2a2016-10-25 13:49:23 -0700171 public void testTaskChangeCallBacks() throws Exception {
172 final Object[] params = new Object[2];
173 final CountDownLatch taskCreatedLaunchLatch = new CountDownLatch(1);
174 final CountDownLatch taskMovedToFrontLatch = new CountDownLatch(1);
175 final CountDownLatch taskRemovedLatch = new CountDownLatch(1);
176 final CountDownLatch taskRemovalStartedLatch = new CountDownLatch(1);
177 final CountDownLatch onDetachedFromWindowLatch = new CountDownLatch(1);
178 registerTaskStackChangedListener(new TaskStackListener() {
179 @Override
180 public void onTaskCreated(int taskId, ComponentName componentName)
181 throws RemoteException {
182 params[0] = taskId;
183 params[1] = componentName;
184 taskCreatedLaunchLatch.countDown();
185 }
186
187 @Override
188 public void onTaskMovedToFront(int taskId) throws RemoteException {
189 params[0] = taskId;
190 taskMovedToFrontLatch.countDown();
191 }
192
193 @Override
194 public void onTaskRemovalStarted(int taskId) {
195 params[0] = taskId;
196 taskRemovalStartedLatch.countDown();
197 }
198
199 @Override
200 public void onTaskRemoved(int taskId) throws RemoteException {
201 params[0] = taskId;
202 taskRemovedLatch.countDown();
203 }
204 });
205
206 final ActivityTaskChangeCallbacks activity =
207 (ActivityTaskChangeCallbacks) startTestActivity(ActivityTaskChangeCallbacks.class);
Andrii Kulian6a6c4f12018-07-16 21:23:33 -0700208 activity.setDetachedFromWindowLatch(onDetachedFromWindowLatch);
Yorke Leebd54c2a2016-10-25 13:49:23 -0700209 final int id = activity.getTaskId();
210
211 // Test for onTaskCreated.
212 waitForCallback(taskCreatedLaunchLatch);
213 assertEquals(id, params[0]);
214 ComponentName componentName = (ComponentName) params[1];
215 assertEquals(ActivityTaskChangeCallbacks.class.getName(), componentName.getClassName());
216
217 // Test for onTaskMovedToFront.
218 assertEquals(1, taskMovedToFrontLatch.getCount());
Ricky Waiaca8a772019-04-04 16:01:06 +0100219 mService.moveTaskToFront(null, getInstrumentation().getContext().getPackageName(), id, 0,
220 null);
Yorke Leebd54c2a2016-10-25 13:49:23 -0700221 waitForCallback(taskMovedToFrontLatch);
222 assertEquals(activity.getTaskId(), params[0]);
223
224 // Test for onTaskRemovalStarted.
225 assertEquals(1, taskRemovalStartedLatch.getCount());
Yunfan Chenc1f3e372019-03-07 10:08:08 +0900226 assertEquals(1, taskRemovedLatch.getCount());
Yorke Leebd54c2a2016-10-25 13:49:23 -0700227 activity.finishAndRemoveTask();
228 waitForCallback(taskRemovalStartedLatch);
229 // onTaskRemovalStarted happens before the activity's window is removed.
Tadashi G. Takaoka74ccec22018-10-23 11:07:13 +0900230 assertFalse(activity.mOnDetachedFromWindowCalled);
Yorke Leebd54c2a2016-10-25 13:49:23 -0700231 assertEquals(id, params[0]);
232
233 // Test for onTaskRemoved.
Yorke Leebd54c2a2016-10-25 13:49:23 -0700234 waitForCallback(taskRemovedLatch);
235 assertEquals(id, params[0]);
Andrii Kulian6a6c4f12018-07-16 21:23:33 -0700236 waitForCallback(onDetachedFromWindowLatch);
Tadashi G. Takaoka74ccec22018-10-23 11:07:13 +0900237 assertTrue(activity.mOnDetachedFromWindowCalled);
Yorke Leebd54c2a2016-10-25 13:49:23 -0700238 }
239
Issei Suzukicac2a502019-04-16 16:52:50 +0200240 @Test
241 public void testTaskOnSingleTaskDisplayDrawn() throws Exception {
242 final Instrumentation instrumentation = getInstrumentation();
243
244 final CountDownLatch activityViewReadyLatch = new CountDownLatch(1);
245 final CountDownLatch singleTaskDisplayDrawnLatch = new CountDownLatch(1);
246 registerTaskStackChangedListener(new TaskStackListener() {
247 @Override
248 public void onSingleTaskDisplayDrawn(int displayId) throws RemoteException {
249 singleTaskDisplayDrawnLatch.countDown();
250 }
251 });
252 final ActivityViewTestActivity activity =
253 (ActivityViewTestActivity) startTestActivity(ActivityViewTestActivity.class);
254 final ActivityView activityView = activity.getActivityView();
255 activityView.setCallback(new ActivityView.StateCallback() {
256 @Override
257 public void onActivityViewReady(ActivityView view) {
258 activityViewReadyLatch.countDown();
259 }
260
261 @Override
262 public void onActivityViewDestroyed(ActivityView view) {
263 }
264 });
265 waitForCallback(activityViewReadyLatch);
266
267 final Context context = instrumentation.getContext();
268 Intent intent = new Intent(context, ActivityInActivityView.class);
269 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
270 activityView.startActivity(intent);
271 waitForCallback(singleTaskDisplayDrawnLatch);
272 }
273
Yorke Leebd54c2a2016-10-25 13:49:23 -0700274 /**
275 * Starts the provided activity and returns the started instance.
276 */
Kazuki Takise9a0be722018-08-08 14:44:15 +0900277 private TestActivity startTestActivity(Class<?> activityClass) throws InterruptedException {
Tadashi G. Takaoka74ccec22018-10-23 11:07:13 +0900278 final ActivityMonitor monitor = new ActivityMonitor(activityClass.getName(), null, false);
279 getInstrumentation().addMonitor(monitor);
280 final Context context = getInstrumentation().getContext();
Mark Renouf0d4bc922019-02-08 17:01:58 -0500281 context.startActivity(
282 new Intent(context, activityClass).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
Tadashi G. Takaoka74ccec22018-10-23 11:07:13 +0900283 final TestActivity activity = (TestActivity) monitor.waitForActivityWithTimeout(1000);
Yorke Leebd54c2a2016-10-25 13:49:23 -0700284 if (activity == null) {
285 throw new RuntimeException("Timed out waiting for Activity");
Jorim Jaggi7b614372016-09-28 15:17:50 +0200286 }
Kazuki Takise9a0be722018-08-08 14:44:15 +0900287 activity.waitForResumeStateChange(true);
Yorke Leebd54c2a2016-10-25 13:49:23 -0700288 return activity;
Jorim Jaggi7b614372016-09-28 15:17:50 +0200289 }
290
Yorke Leebd54c2a2016-10-25 13:49:23 -0700291 private void registerTaskStackChangedListener(ITaskStackListener listener) throws Exception {
292 mTaskStackListener = listener;
Wale Ogunwale04d9cb52018-04-30 13:55:07 -0700293 ActivityTaskManager.getService().registerTaskStackListener(listener);
Jorim Jaggi7b614372016-09-28 15:17:50 +0200294 }
295
Yorke Leebd54c2a2016-10-25 13:49:23 -0700296 private void waitForCallback(CountDownLatch latch) {
297 try {
Yunfan Chen968cc7f2019-02-15 20:14:14 +0900298 final boolean result = latch.await(4, TimeUnit.SECONDS);
Yorke Leebd54c2a2016-10-25 13:49:23 -0700299 if (!result) {
300 throw new RuntimeException("Timed out waiting for task stack change notification");
301 }
Tadashi G. Takaoka74ccec22018-10-23 11:07:13 +0900302 } catch (InterruptedException e) {
303 }
Jorim Jaggi7b614372016-09-28 15:17:50 +0200304 }
305
Kazuki Takise9a0be722018-08-08 14:44:15 +0900306 public static class TestActivity extends Activity {
307 boolean mIsResumed = false;
308
309 @Override
310 protected void onPostResume() {
311 super.onPostResume();
312 synchronized (this) {
313 mIsResumed = true;
314 notifyAll();
315 }
316 }
317
318 @Override
319 protected void onPause() {
320 super.onPause();
321 synchronized (this) {
322 mIsResumed = false;
323 notifyAll();
324 }
325 }
326
327 /**
328 * If isResumed is {@code true}, sleep the thread until the activity is resumed.
329 * if {@code false}, sleep the thread until the activity is paused.
330 */
Tadashi G. Takaoka74ccec22018-10-23 11:07:13 +0900331 @SuppressWarnings("WaitNotInLoop")
Kazuki Takise9a0be722018-08-08 14:44:15 +0900332 public void waitForResumeStateChange(boolean isResumed) throws InterruptedException {
333 synchronized (this) {
334 if (mIsResumed == isResumed) {
335 return;
336 }
337 wait(5000);
338 }
Tadashi G. Takaoka74ccec22018-10-23 11:07:13 +0900339 assertEquals("The activity resume state change timed out", isResumed, mIsResumed);
Kazuki Takise9a0be722018-08-08 14:44:15 +0900340 }
341 }
342
343 public static class ActivityA extends TestActivity {
Jorim Jaggi7b614372016-09-28 15:17:50 +0200344
345 private boolean mActivityBLaunched = false;
346
347 @Override
348 protected void onPostResume() {
349 super.onPostResume();
350 if (mActivityBLaunched) {
351 return;
352 }
353 mActivityBLaunched = true;
354 finish();
355 startActivity(new Intent(this, ActivityB.class));
356 }
357 }
358
Kazuki Takise9a0be722018-08-08 14:44:15 +0900359 public static class ActivityB extends TestActivity {
Jorim Jaggi7b614372016-09-28 15:17:50 +0200360
361 @Override
362 protected void onPostResume() {
363 super.onPostResume();
364 synchronized (sLock) {
365 sTaskStackChangedCalled = false;
366 }
367 sActivityBResumed = true;
368 finish();
369 }
370 }
Yorke Leebd54c2a2016-10-25 13:49:23 -0700371
Kazuki Takise9a0be722018-08-08 14:44:15 +0900372 public static class ActivityRequestedOrientationChange extends TestActivity {
Yorke Leebd54c2a2016-10-25 13:49:23 -0700373 @Override
374 protected void onPostResume() {
375 super.onPostResume();
376 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
Yunfan Chen968cc7f2019-02-15 20:14:14 +0900377 synchronized (sLock) {
378 // Hold the lock to ensure no one is trying to access fields of this Activity in
379 // this test.
380 finish();
381 }
Yorke Leebd54c2a2016-10-25 13:49:23 -0700382 }
383 }
384
Kazuki Takise9a0be722018-08-08 14:44:15 +0900385 public static class ActivityTaskDescriptionChange extends TestActivity {
Yorke Leebd54c2a2016-10-25 13:49:23 -0700386 @Override
387 protected void onPostResume() {
388 super.onPostResume();
389 setTaskDescription(new TaskDescription("Test Label"));
Yunfan Chen968cc7f2019-02-15 20:14:14 +0900390 synchronized (sLock) {
391 // Hold the lock to ensure no one is trying to access fields of this Activity in
392 // this test.
393 finish();
394 }
Yorke Leebd54c2a2016-10-25 13:49:23 -0700395 }
396 }
397
Kazuki Takise9a0be722018-08-08 14:44:15 +0900398 public static class ActivityTaskChangeCallbacks extends TestActivity {
Tadashi G. Takaoka74ccec22018-10-23 11:07:13 +0900399 public boolean mOnDetachedFromWindowCalled = false;
400 private CountDownLatch mOnDetachedFromWindowCountDownLatch;
Yorke Leebd54c2a2016-10-25 13:49:23 -0700401
402 @Override
403 public void onDetachedFromWindow() {
Tadashi G. Takaoka74ccec22018-10-23 11:07:13 +0900404 mOnDetachedFromWindowCalled = true;
405 mOnDetachedFromWindowCountDownLatch.countDown();
Andrii Kulian6a6c4f12018-07-16 21:23:33 -0700406 }
407
408 void setDetachedFromWindowLatch(CountDownLatch countDownLatch) {
Tadashi G. Takaoka74ccec22018-10-23 11:07:13 +0900409 mOnDetachedFromWindowCountDownLatch = countDownLatch;
Yorke Leebd54c2a2016-10-25 13:49:23 -0700410 }
411 }
Issei Suzukicac2a502019-04-16 16:52:50 +0200412
413 public static class ActivityViewTestActivity extends TestActivity {
414 private ActivityView mActivityView;
415
416 @Override
417 public void onCreate(Bundle savedInstanceState) {
418 super.onCreate(savedInstanceState);
419
420 mActivityView = new ActivityView(this, null /* attrs */, 0 /* defStyle */,
421 true /* singleTaskInstance */);
422 setContentView(mActivityView);
423
424 ViewGroup.LayoutParams layoutParams = mActivityView.getLayoutParams();
425 layoutParams.width = MATCH_PARENT;
426 layoutParams.height = MATCH_PARENT;
427 mActivityView.requestLayout();
428 }
429
430 ActivityView getActivityView() {
431 return mActivityView;
432 }
433 }
434
435 // Activity that has {@link android.R.attr#resizeableActivity} attribute set to {@code true}
436 public static class ActivityInActivityView extends TestActivity {}
Jorim Jaggi7b614372016-09-28 15:17:50 +0200437}