140db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi/*
240db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi * Copyright (C) 2016 The Android Open Source Project
340db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi *
440db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi * Licensed under the Apache License, Version 2.0 (the "License");
540db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi * you may not use this file except in compliance with the License.
640db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi * You may obtain a copy of the License at
740db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi *
840db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi *      http://www.apache.org/licenses/LICENSE-2.0
940db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi *
1040db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi * Unless required by applicable law or agreed to in writing, software
1140db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi * distributed under the License is distributed on an "AS IS" BASIS,
1240db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1340db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi * See the License for the specific language governing permissions and
1440db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi * limitations under the License
1540db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi */
1640db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi
1740db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggipackage com.android.systemui.statusbar.phone;
1840db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi
1940db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggiimport android.animation.ValueAnimator;
20aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monkimport android.content.Context;
2149fa016a98cef9940c883b3f620a0e90537a204eJason Monkimport android.os.Bundle;
2240db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggiimport android.os.Handler;
2340db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggiimport android.os.SystemClock;
241bf6077afeecec35fe0d57072e851f9810e937cdAdrian Roosimport android.util.TimeUtils;
2540db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi
26aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monkimport com.android.systemui.Dependency;
27ff46d4c935752f22808865b2fb60f883ae02a1c5Jorim Jaggiimport com.android.systemui.Dumpable;
2840db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggiimport com.android.systemui.Interpolators;
29aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monkimport com.android.systemui.SysUiServiceProvider;
30aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monkimport com.android.systemui.statusbar.CommandQueue;
31aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monkimport com.android.systemui.statusbar.CommandQueue.Callbacks;
32aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monkimport com.android.systemui.statusbar.policy.KeyguardMonitor;
3340db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi
341bf6077afeecec35fe0d57072e851f9810e937cdAdrian Roosimport java.io.FileDescriptor;
351bf6077afeecec35fe0d57072e851f9810e937cdAdrian Roosimport java.io.PrintWriter;
361bf6077afeecec35fe0d57072e851f9810e937cdAdrian Roos
3740db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi/**
3840db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi * Class to control all aspects about light bar changes.
3940db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi */
40aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monkpublic class LightBarTransitionsController implements Dumpable, Callbacks {
4140db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi
4240db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    public static final long DEFAULT_TINT_ANIMATION_DURATION = 120;
4349fa016a98cef9940c883b3f620a0e90537a204eJason Monk    private static final String EXTRA_DARK_INTENSITY = "dark_intensity";
4440db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi
4540db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    private final Handler mHandler;
4640db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    private final DarkIntensityApplier mApplier;
47aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monk    private final KeyguardMonitor mKeyguardMonitor;
4840db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi
4940db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    private boolean mTransitionDeferring;
5040db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    private long mTransitionDeferringStartTime;
5140db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    private long mTransitionDeferringDuration;
5240db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    private boolean mTransitionPending;
5340db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    private boolean mTintChangePending;
5440db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    private float mPendingDarkIntensity;
5540db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    private ValueAnimator mTintAnimator;
5640db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    private float mDarkIntensity;
5749fa016a98cef9940c883b3f620a0e90537a204eJason Monk    private float mNextDarkIntensity;
5840db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    private final Runnable mTransitionDeferringDoneRunnable = new Runnable() {
5940db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        @Override
6040db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        public void run() {
6140db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi            mTransitionDeferring = false;
6240db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        }
6340db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    };
6440db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi
65aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monk    public LightBarTransitionsController(Context context, DarkIntensityApplier applier) {
6640db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        mApplier = applier;
6740db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        mHandler = new Handler();
68aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monk        mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
69aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monk        SysUiServiceProvider.getComponent(context, CommandQueue.class)
70aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monk                .addCallbacks(this);
71aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monk    }
72aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monk
73aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monk    public void destroy(Context context) {
74aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monk        SysUiServiceProvider.getComponent(context, CommandQueue.class)
75aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monk                .removeCallbacks(this);
7640db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    }
7740db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi
7849fa016a98cef9940c883b3f620a0e90537a204eJason Monk    public void saveState(Bundle outState) {
7949fa016a98cef9940c883b3f620a0e90537a204eJason Monk        float intensity = mTintAnimator != null && mTintAnimator.isRunning()
8049fa016a98cef9940c883b3f620a0e90537a204eJason Monk                ?  mNextDarkIntensity : mDarkIntensity;
8149fa016a98cef9940c883b3f620a0e90537a204eJason Monk        outState.putFloat(EXTRA_DARK_INTENSITY, intensity);
8249fa016a98cef9940c883b3f620a0e90537a204eJason Monk    }
8349fa016a98cef9940c883b3f620a0e90537a204eJason Monk
8449fa016a98cef9940c883b3f620a0e90537a204eJason Monk    public void restoreState(Bundle savedInstanceState) {
8549fa016a98cef9940c883b3f620a0e90537a204eJason Monk        setIconTintInternal(savedInstanceState.getFloat(EXTRA_DARK_INTENSITY, 0));
8649fa016a98cef9940c883b3f620a0e90537a204eJason Monk    }
8749fa016a98cef9940c883b3f620a0e90537a204eJason Monk
88aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monk    @Override
89aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monk    public void appTransitionPending(boolean forced) {
90aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monk        if (mKeyguardMonitor.isKeyguardGoingAway() && !forced) {
91aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monk            return;
92aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monk        }
9340db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        mTransitionPending = true;
9440db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    }
9540db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi
96aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monk    @Override
9740db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    public void appTransitionCancelled() {
9840db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        if (mTransitionPending && mTintChangePending) {
9940db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi            mTintChangePending = false;
10040db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi            animateIconTint(mPendingDarkIntensity, 0 /* delay */, DEFAULT_TINT_ANIMATION_DURATION);
10140db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        }
10240db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        mTransitionPending = false;
10340db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    }
10440db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi
105aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monk    @Override
106aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monk    public void appTransitionStarting(long startTime, long duration, boolean forced) {
107aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monk        if (mKeyguardMonitor.isKeyguardGoingAway() && !forced) {
108aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monk            return;
109aa573e9e8632552d1fa8bdd6b0ee408ff9a93a6bJason Monk        }
11040db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        if (mTransitionPending && mTintChangePending) {
11140db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi            mTintChangePending = false;
11240db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi            animateIconTint(mPendingDarkIntensity,
11340db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi                    Math.max(0, startTime - SystemClock.uptimeMillis()),
11440db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi                    duration);
11540db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi
11640db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        } else if (mTransitionPending) {
11740db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi
11840db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi            // If we don't have a pending tint change yet, the change might come in the future until
11940db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi            // startTime is reached.
12040db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi            mTransitionDeferring = true;
12140db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi            mTransitionDeferringStartTime = startTime;
12240db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi            mTransitionDeferringDuration = duration;
12340db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi            mHandler.removeCallbacks(mTransitionDeferringDoneRunnable);
12440db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi            mHandler.postAtTime(mTransitionDeferringDoneRunnable, startTime);
12540db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        }
12640db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        mTransitionPending = false;
12740db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    }
12840db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi
12940db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    public void setIconsDark(boolean dark, boolean animate) {
13040db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        if (!animate) {
13140db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi            setIconTintInternal(dark ? 1.0f : 0.0f);
132ff46d4c935752f22808865b2fb60f883ae02a1c5Jorim Jaggi            mNextDarkIntensity = dark ? 1.0f : 0.0f;
13340db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        } else if (mTransitionPending) {
13440db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi            deferIconTintChange(dark ? 1.0f : 0.0f);
13540db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        } else if (mTransitionDeferring) {
13640db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi            animateIconTint(dark ? 1.0f : 0.0f,
13740db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi                    Math.max(0, mTransitionDeferringStartTime - SystemClock.uptimeMillis()),
13840db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi                    mTransitionDeferringDuration);
13940db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        } else {
14040db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi            animateIconTint(dark ? 1.0f : 0.0f, 0 /* delay */, DEFAULT_TINT_ANIMATION_DURATION);
14140db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        }
14240db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    }
14340db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi
14440db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    public float getCurrentDarkIntensity() {
14540db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        return mDarkIntensity;
14640db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    }
14740db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi
14840db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    private void deferIconTintChange(float darkIntensity) {
14940db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        if (mTintChangePending && darkIntensity == mPendingDarkIntensity) {
15040db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi            return;
15140db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        }
15240db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        mTintChangePending = true;
15340db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        mPendingDarkIntensity = darkIntensity;
15440db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    }
15540db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi
15640db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    private void animateIconTint(float targetDarkIntensity, long delay,
15740db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi            long duration) {
158ff46d4c935752f22808865b2fb60f883ae02a1c5Jorim Jaggi        if (mNextDarkIntensity == targetDarkIntensity) {
159ff46d4c935752f22808865b2fb60f883ae02a1c5Jorim Jaggi            return;
160ff46d4c935752f22808865b2fb60f883ae02a1c5Jorim Jaggi        }
16140db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        if (mTintAnimator != null) {
16240db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi            mTintAnimator.cancel();
16340db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        }
16449fa016a98cef9940c883b3f620a0e90537a204eJason Monk        mNextDarkIntensity = targetDarkIntensity;
16540db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        mTintAnimator = ValueAnimator.ofFloat(mDarkIntensity, targetDarkIntensity);
16640db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        mTintAnimator.addUpdateListener(
16740db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi                animation -> setIconTintInternal((Float) animation.getAnimatedValue()));
16840db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        mTintAnimator.setDuration(duration);
16940db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        mTintAnimator.setStartDelay(delay);
1700c1157efb3be02bc36a7cb41ee619091c10547feLucas Dupin        mTintAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
17140db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        mTintAnimator.start();
17240db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    }
17340db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi
17440db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    private void setIconTintInternal(float darkIntensity) {
17540db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        mDarkIntensity = darkIntensity;
17640db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        mApplier.applyDarkIntensity(darkIntensity);
17740db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    }
17840db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi
1791bf6077afeecec35fe0d57072e851f9810e937cdAdrian Roos    @Override
1801bf6077afeecec35fe0d57072e851f9810e937cdAdrian Roos    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1811bf6077afeecec35fe0d57072e851f9810e937cdAdrian Roos        pw.print("  mTransitionDeferring="); pw.print(mTransitionDeferring);
1821bf6077afeecec35fe0d57072e851f9810e937cdAdrian Roos        if (mTransitionDeferring) {
1831bf6077afeecec35fe0d57072e851f9810e937cdAdrian Roos            pw.println();
1841bf6077afeecec35fe0d57072e851f9810e937cdAdrian Roos            pw.print("   mTransitionDeferringStartTime=");
1851bf6077afeecec35fe0d57072e851f9810e937cdAdrian Roos            pw.println(TimeUtils.formatUptime(mTransitionDeferringStartTime));
1861bf6077afeecec35fe0d57072e851f9810e937cdAdrian Roos
1871bf6077afeecec35fe0d57072e851f9810e937cdAdrian Roos            pw.print("   mTransitionDeferringDuration=");
1881bf6077afeecec35fe0d57072e851f9810e937cdAdrian Roos            TimeUtils.formatDuration(mTransitionDeferringDuration, pw);
1891bf6077afeecec35fe0d57072e851f9810e937cdAdrian Roos            pw.println();
1901bf6077afeecec35fe0d57072e851f9810e937cdAdrian Roos        }
1911bf6077afeecec35fe0d57072e851f9810e937cdAdrian Roos        pw.print("  mTransitionPending="); pw.print(mTransitionPending);
1921bf6077afeecec35fe0d57072e851f9810e937cdAdrian Roos        pw.print(" mTintChangePending="); pw.println(mTintChangePending);
1931bf6077afeecec35fe0d57072e851f9810e937cdAdrian Roos
1941bf6077afeecec35fe0d57072e851f9810e937cdAdrian Roos        pw.print("  mPendingDarkIntensity="); pw.print(mPendingDarkIntensity);
1951bf6077afeecec35fe0d57072e851f9810e937cdAdrian Roos        pw.print(" mDarkIntensity="); pw.print(mDarkIntensity);
1961bf6077afeecec35fe0d57072e851f9810e937cdAdrian Roos        pw.print(" mNextDarkIntensity="); pw.println(mNextDarkIntensity);
1971bf6077afeecec35fe0d57072e851f9810e937cdAdrian Roos    }
1981bf6077afeecec35fe0d57072e851f9810e937cdAdrian Roos
19940db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    /**
20040db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi     * Interface to apply a specific dark intensity.
20140db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi     */
20240db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    public interface DarkIntensityApplier {
20340db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi        void applyDarkIntensity(float darkIntensity);
20440db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi    }
20540db029cfeb263b5b672ca687c347e58ed2ad2aeJorim Jaggi}
206