diff --git a/jami-android/app/src/main/java/cx/ring/views/twopane/TwoPaneLayout.java b/jami-android/app/src/main/java/cx/ring/views/twopane/TwoPaneLayout.java
deleted file mode 100644
index ffe4de12f77f3c56d35da7a612c6285595ce6d7f..0000000000000000000000000000000000000000
--- a/jami-android/app/src/main/java/cx/ring/views/twopane/TwoPaneLayout.java
+++ /dev/null
@@ -1,885 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package cx.ring.views.twopane;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.view.accessibility.AccessibilityEvent;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.content.ContextCompat;
-import androidx.core.view.AccessibilityDelegateCompat;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
-import androidx.core.view.animation.PathInterpolatorCompat;
-import androidx.customview.view.AbsSavedState;
-import androidx.customview.widget.Openable;
-import androidx.transition.ChangeBounds;
-import androidx.transition.Transition;
-import androidx.transition.TransitionManager;
-import androidx.window.layout.FoldingFeature;
-import androidx.window.layout.WindowInfoTracker;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.Executor;
-
-/**
- * TwoPaneLayout provides a horizontal, multi-pane layout for use at the top level
- * of a UI. A left (or start) pane is treated as a content list or browser, subordinate to a
- * primary detail view for displaying content.
- *
- * <p>Child views overlap if their combined width exceeds the available width
- * in the TwoPaneLayout. Each of child views is expand out to fill the available width in
- * the TwoPaneLayout.</p>
- *
- * <p>Thanks to this behavior, TwoPaneLayout may be suitable for creating layouts
- * that can smoothly adapt across many different screen sizes, expanding out fully on larger
- * screens and collapsing on smaller screens.</p>
- *
- * <p>TwoPaneLayout is distinct from a navigation drawer as described in the design
- * guide and should not be used in the same scenarios. TwoPaneLayout should be thought
- * of only as a way to allow a two-pane layout normally used on larger screens to adapt to smaller
- * screens in a natural way. The interaction patterns expressed by TwoPaneLayout imply
- * a physicality and direct information hierarchy between panes that does not necessarily exist
- * in a scenario where a navigation drawer should be used instead.</p>
- *
- * <p>Appropriate uses of TwoPaneLayout include pairings of panes such as a contact list and
- * subordinate interactions with those contacts, or an email thread list with the content pane
- * displaying the contents of the selected thread. Inappropriate uses of SlidingPaneLayout include
- * switching between disparate functions of your app, such as jumping from a social stream view
- * to a view of your personal profile - cases such as this should use the navigation drawer
- * pattern instead. ({@link androidx.drawerlayout.widget.DrawerLayout DrawerLayout} implements
- * this pattern.)</p>
- *
- * <p>Like {@link android.widget.LinearLayout LinearLayout}, TwoPaneLayout supports
- * the use of the layout parameter <code>layout_weight</code> on child views to determine
- * how to divide leftover space after measurement is complete. It is only relevant for width.
- * When views do not overlap weight behaves as it does in a LinearLayout.</p>
- */
-public class TwoPaneLayout extends ViewGroup implements Openable {
-    private static final String TAG = "TwoPaneLayout";
-
-    /** Class name may be obfuscated by Proguard. Hardcode the string for accessibility usage. */
-    private static final String ACCESSIBILITY_CLASS_NAME =
-            "cx.ring.views.twopane.TwoPaneLayout";
-
-    /**
-     * True if a panel can slide with the current measurements
-     */
-    private boolean mCanSlide;
-
-    /**
-     * The child view that can slide, if any.
-     */
-    private View mSlideableView;
-
-    /**
-     * How far the panel is offset from its usual position.
-     * range [0, 1] where 0 = open, 1 = closed.
-     */
-    private boolean isOpened = false;
-
-    private final List<PanelListener> mPanelListeners = new CopyOnWriteArrayList<>();
-
-    /**
-     * Stores whether or not the pane was open the last time it was slideable.
-     * If open/close operations are invoked this state is modified. Used by
-     * instance state save/restore.
-     */
-    private boolean mPreservedOpenState;
-    private boolean mFirstLayout = true;
-
-    private final Rect mTmpRect = new Rect();
-
-    FoldingFeature mFoldingFeature;
-
-    /**
-     * Listener for monitoring events about sliding panes.
-     */
-    public interface PanelListener {
-        /**
-         * Called when a detail view becomes slid completely open.
-         *
-         * @param panel The detail view that was slid to an open position
-         */
-        void onPanelOpened(@NonNull View panel);
-
-        /**
-         * Called when a detail view becomes slid completely closed.
-         *
-         * @param panel The detail view that was slid to a closed position
-         */
-        void onPanelClosed(@NonNull View panel);
-    }
-
-    private final FoldingFeatureObserver.OnFoldingFeatureChangeListener mOnFoldingFeatureChangeListener =
-            new FoldingFeatureObserver.OnFoldingFeatureChangeListener() {
-                @Override
-                public void onFoldingFeatureChange(@NonNull FoldingFeature foldingFeature) {
-                    mFoldingFeature = foldingFeature;
-                    // Start transition animation when folding feature changed
-                    Transition changeBounds = new ChangeBounds();
-                    changeBounds.setDuration(300L);
-                    changeBounds.setInterpolator(PathInterpolatorCompat.create(0.2f, 0, 0, 1));
-                    TransitionManager.beginDelayedTransition(TwoPaneLayout.this, changeBounds);
-                    requestLayout();
-                }
-            };
-
-    private FoldingFeatureObserver mFoldingFeatureObserver;
-
-    public TwoPaneLayout(@NonNull Context context) {
-        this(context, null);
-    }
-
-    public TwoPaneLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public TwoPaneLayout(@NonNull Context context, @Nullable AttributeSet attrs,
-                         int defStyle) {
-        super(context, attrs, defStyle);
-        ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegate());
-        ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
-
-        WindowInfoTracker repo = WindowInfoTracker.getOrCreate(context);
-        Executor mainExecutor = ContextCompat.getMainExecutor(context);
-        mFoldingFeatureObserver = new FoldingFeatureObserver(repo, mainExecutor);
-        mFoldingFeatureObserver.setOnFoldingFeatureChangeListener(mOnFoldingFeatureChangeListener);
-    }
-
-    /**
-     * Adds the specified listener to the list of listeners that will be notified of
-     * panel slide events.
-     *
-     * @param listener Listener to notify when panel slide events occur.
-     * @see #removePanelListener(PanelListener)
-     */
-    public void addPanelListener(@NonNull PanelListener listener) {
-        mPanelListeners.add(listener);
-    }
-
-    /**
-     * Removes the specified listener from the list of listeners that will be notified of
-     * panel slide events.
-     *
-     * @param listener Listener to remove from being notified of panel slide events
-     * @see #addPanelListener(PanelListener)
-     */
-    public void removePanelListener(@NonNull PanelListener listener) {
-        mPanelListeners.remove(listener);
-    }
-
-    void dispatchOnPanelOpened(@NonNull View panel) {
-        for (PanelListener listener : mPanelListeners) {
-            listener.onPanelOpened(panel);
-        }
-        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-    }
-
-    void dispatchOnPanelClosed(@NonNull View panel) {
-        for (PanelListener listener : mPanelListeners) {
-            listener.onPanelClosed(panel);
-        }
-        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mFirstLayout = true;
-        if (mFoldingFeatureObserver != null) {
-            Activity activity = getActivityOrNull(getContext());
-            if (activity != null) {
-                mFoldingFeatureObserver.registerLayoutStateChangeCallback(activity);
-            }
-        }
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        mFirstLayout = true;
-        if (mFoldingFeatureObserver != null) {
-            mFoldingFeatureObserver.unregisterLayoutStateChangeCallback();
-        }
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
-        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
-        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
-        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
-
-        int layoutHeight = 0;
-        int maxLayoutHeight = 0;
-        switch (heightMode) {
-            case MeasureSpec.EXACTLY:
-                layoutHeight = maxLayoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
-                break;
-            case MeasureSpec.AT_MOST:
-                maxLayoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
-                break;
-        }
-
-        float weightSum = 0;
-        boolean canSlide = false;
-        final int widthAvailable = Math.max(widthSize - getPaddingLeft() - getPaddingRight(), 0);
-        int widthRemaining = widthAvailable;
-        final int childCount = getChildCount();
-
-        if (childCount > 2) {
-            Log.e(TAG, "onMeasure: More than two child views are not supported.");
-        }
-
-        // We'll find the current one below.
-        mSlideableView = null;
-
-        // First pass. Measure based on child LayoutParams width/height.
-        // Weight will incur a second pass.
-        for (int i = 0; i < childCount; i++) {
-            final View child = getChildAt(i);
-            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
-            if (child.getVisibility() == GONE) {
-                continue;
-            }
-
-            if (lp.weight > 0) {
-                weightSum += lp.weight;
-
-                // If we have no width, weight is the only contributor to the final size.
-                // Measure this view on the weight pass only.
-                if (lp.width == 0) continue;
-            }
-
-            int childWidthSpec;
-            final int horizontalMargin = lp.leftMargin + lp.rightMargin;
-
-            int childWidthSize = Math.max(widthAvailable - horizontalMargin, 0);
-            // When the parent width spec is UNSPECIFIED, measure each of child to get its
-            // desired width.
-            if (lp.width == LayoutParams.WRAP_CONTENT) {
-                childWidthSpec = MeasureSpec.makeMeasureSpec(childWidthSize,
-                        widthMode == MeasureSpec.UNSPECIFIED ? widthMode : MeasureSpec.AT_MOST);
-            } else if (lp.width == LayoutParams.MATCH_PARENT) {
-                childWidthSpec = MeasureSpec.makeMeasureSpec(childWidthSize, widthMode);
-            } else {
-                childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
-            }
-
-            int childHeightSpec = getChildMeasureSpec(heightMeasureSpec,
-                    getPaddingTop() + getPaddingBottom(), lp.height);
-            child.measure(childWidthSpec, childHeightSpec);
-            final int childWidth = child.getMeasuredWidth();
-            final int childHeight = child.getMeasuredHeight();
-
-            if (childHeight > layoutHeight) {
-                if (heightMode == MeasureSpec.AT_MOST) {
-                    layoutHeight = Math.min(childHeight, maxLayoutHeight);
-                } else if (heightMode == MeasureSpec.UNSPECIFIED) {
-                    layoutHeight = childHeight;
-                }
-            }
-
-            widthRemaining -= childWidth;
-            // Skip first child (list pane), the list pane is always a non-sliding pane.
-            if (i == 0) {
-                continue;
-            }
-            canSlide |= lp.slideable = widthRemaining < 0;
-            if (lp.slideable) {
-                mSlideableView = child;
-            }
-        }
-        // Second pass. Resolve weight.
-        // Child views overlap when the combined width of child views cannot fit into the
-        // available width. Each of child views is sized to fill all available space. If there is
-        // no overlap, distribute the extra width proportionally to weight.
-        if (canSlide || weightSum > 0) {
-            for (int i = 0; i < childCount; i++) {
-                final View child = getChildAt(i);
-                if (child.getVisibility() == GONE) {
-                    continue;
-                }
-
-                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-                final boolean skippedFirstPass = lp.width == 0 && lp.weight > 0;
-                final int measuredWidth = skippedFirstPass ? 0 : child.getMeasuredWidth();
-                int newWidth = measuredWidth;
-                int childWidthSpec = 0;
-                if (canSlide) {
-                    // Child view consumes available space if the combined width cannot fit into
-                    // the layout available width.
-                    final int horizontalMargin = lp.leftMargin + lp.rightMargin;
-                    newWidth = widthAvailable - horizontalMargin;
-                    childWidthSpec = MeasureSpec.makeMeasureSpec(
-                            newWidth, MeasureSpec.EXACTLY);
-
-                } else if (lp.weight > 0) {
-                    // Distribute the extra width proportionally similar to LinearLayout
-                    final int widthToDistribute = Math.max(0, widthRemaining);
-                    final int addedWidth = (int) (lp.weight * widthToDistribute / weightSum);
-                    newWidth = measuredWidth + addedWidth;
-                    childWidthSpec = MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY);
-                }
-                final int childHeightSpec = measureChildHeight(child, heightMeasureSpec,
-                        getPaddingTop() + getPaddingBottom());
-                if (measuredWidth != newWidth) {
-                    child.measure(childWidthSpec, childHeightSpec);
-                    final int childHeight = child.getMeasuredHeight();
-                    if (childHeight > layoutHeight) {
-                        if (heightMode == MeasureSpec.AT_MOST) {
-                            layoutHeight = Math.min(childHeight, maxLayoutHeight);
-                        } else if (heightMode == MeasureSpec.UNSPECIFIED) {
-                            layoutHeight = childHeight;
-                        }
-                    }
-                }
-            }
-        }
-
-        // At this point, all child views have been measured. Calculate the device fold position
-        // in the view. Update the split position to where the fold when it exists.
-        ArrayList<Rect> splitViews = splitViewPositions();
-
-        if (splitViews != null && !canSlide) {
-            for (int i = 0; i < childCount; i++) {
-                final View child = getChildAt(i);
-
-                if (child.getVisibility() == GONE) {
-                    continue;
-                }
-
-                final Rect splitView = splitViews.get(i);
-                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
-                // If child view cannot fit in the separating view, expand the child view to fill
-                // available space.
-                final int horizontalMargin = lp.leftMargin + lp.rightMargin;
-                final int childHeightSpec = MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(),
-                        MeasureSpec.EXACTLY);
-                int childWidthSpec = MeasureSpec.makeMeasureSpec(splitView.width(),
-                        MeasureSpec.AT_MOST);
-                child.measure(childWidthSpec, childHeightSpec);
-                if ((child.getMeasuredWidthAndState() & MEASURED_STATE_TOO_SMALL) != 0 || (
-                        getMinimumWidth(child) != 0
-                                && splitView.width() < getMinimumWidth(child))) {
-                    childWidthSpec = MeasureSpec.makeMeasureSpec(widthAvailable - horizontalMargin,
-                            MeasureSpec.EXACTLY);
-                    child.measure(childWidthSpec, childHeightSpec);
-                    // Skip first child (list pane), the list pane is always a non-sliding pane.
-                    if (i == 0) {
-                        continue;
-                    }
-                    canSlide = lp.slideable = true;
-                    mSlideableView = child;
-                } else {
-                    childWidthSpec = MeasureSpec.makeMeasureSpec(splitView.width(),
-                            MeasureSpec.EXACTLY);
-                    child.measure(childWidthSpec, childHeightSpec);
-                }
-            }
-        }
-
-        final int measuredWidth = widthSize;
-        final int measuredHeight = layoutHeight + getPaddingTop() + getPaddingBottom();
-
-        setMeasuredDimension(measuredWidth, measuredHeight);
-        mCanSlide = canSlide;
-    }
-
-    private static int getMinimumWidth(View child) {
-        return ViewCompat.getMinimumWidth(child);
-    }
-
-    private static int measureChildHeight(@NonNull View child,
-            int spec, int padding) {
-        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-        final int childHeightSpec;
-        final boolean skippedFirstPass = lp.width == 0 && lp.weight > 0;
-        if (skippedFirstPass) {
-            // This was skipped the first time; figure out a real height spec.
-            childHeightSpec = getChildMeasureSpec(spec, padding, lp.height);
-
-        } else {
-            childHeightSpec = MeasureSpec.makeMeasureSpec(
-                    child.getMeasuredHeight(), MeasureSpec.EXACTLY);
-        }
-        return childHeightSpec;
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        final boolean isLayoutRtl = isLayoutRtlSupport();
-        final int width = r - l;
-        final int paddingStart = isLayoutRtl ? getPaddingRight() : getPaddingLeft();
-        final int paddingEnd = isLayoutRtl ? getPaddingLeft() : getPaddingRight();
-        final int paddingTop = getPaddingTop();
-
-        final int childCount = getChildCount();
-        int xStart = paddingStart;
-        int nextXStart = xStart;
-
-        if (mFirstLayout) {
-            isOpened = mCanSlide && mPreservedOpenState;
-        }
-
-        for (int i = 0; i < childCount; i++) {
-            final View child = getChildAt(i);
-
-            if (child.getVisibility() == GONE) {
-                continue;
-            }
-
-            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
-            final int childWidth = child.getMeasuredWidth();
-            int offset = 0;
-
-            if (lp.slideable) {
-                final int margin = lp.leftMargin + lp.rightMargin;
-                final int range = Math.min(nextXStart, width - paddingEnd) - xStart - margin;
-                final int lpMargin = isLayoutRtl ? lp.rightMargin : lp.leftMargin;
-                final int pos = (isOpened) ? 0 : range;
-                xStart += pos + lpMargin;
-            } else {
-                xStart = nextXStart;
-            }
-
-            final int childRight;
-            final int childLeft;
-            if (isLayoutRtl) {
-                childRight = width - xStart + offset;
-                childLeft = childRight - childWidth;
-            } else {
-                childLeft = xStart - offset;
-                childRight = childLeft + childWidth;
-            }
-
-            final int childTop = paddingTop;
-            final int childBottom = childTop + child.getMeasuredHeight();
-            child.layout(childLeft, paddingTop, childRight, childBottom);
-
-            // If a folding feature separates the content, we use its width as the extra
-            // offset for the next child, in order to avoid rendering the content under it.
-            int nextXOffset = 0;
-            if (mFoldingFeature != null
-                    && mFoldingFeature.getOrientation() == FoldingFeature.Orientation.VERTICAL
-                    && mFoldingFeature.isSeparating()) {
-                nextXOffset = mFoldingFeature.getBounds().width();
-            }
-            nextXStart += child.getWidth() + Math.abs(nextXOffset);
-        }
-
-        mFirstLayout = false;
-    }
-
-    @Override
-    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        super.onSizeChanged(w, h, oldw, oldh);
-        // Recalculate sliding panes and their details
-        if (w != oldw) {
-            mFirstLayout = true;
-        }
-    }
-
-    @Override
-    public void requestChildFocus(View child, View focused) {
-        super.requestChildFocus(child, focused);
-        if (!isInTouchMode() && !mCanSlide) {
-            mPreservedOpenState = child == mSlideableView;
-        }
-    }
-
-    /**
-     * Close the detail view if it is currently slideable. If first layout
-     * has already completed this will animate.
-     *
-     * @return true if the pane was slideable and is now closed/in the process of closing
-     */
-    public boolean closePane() {
-        if (!mCanSlide) {
-            mPreservedOpenState = false;
-        }
-        if (mFirstLayout || slideTo(false)) {
-            mPreservedOpenState = false;
-            return true;
-        }
-        return false;
-    }
-
-
-    /**
-     * Open the detail view if it is currently slideable. If first layout
-     * has already completed this will animate.
-     *
-     * @return true if the pane was slideable and is now open/in the process of opening
-     */
-    public boolean openPane() {
-        if (!mCanSlide) {
-            mPreservedOpenState = true;
-        }
-        if (mFirstLayout || slideTo(true)) {
-            mPreservedOpenState = true;
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Open the detail view if it is currently slideable. If first layout
-     * has already completed this will animate.
-     */
-    @Override
-    public void open() {
-        openPane();
-    }
-
-    /**
-     * Close the detail view if it is currently slideable. If first layout
-     * has already completed this will animate.
-     */
-    @Override
-    public void close() {
-        closePane();
-    }
-
-    /**
-     * Check if the detail view is completely open. It can be open either because the slider
-     * itself is open revealing the detail view, or if all content fits without sliding.
-     *
-     * @return true if the detail view is completely open
-     */
-    @Override
-    public boolean isOpen() {
-        return !mCanSlide || isOpened;
-    }
-
-    /**
-     * Check if both the list and detail view panes in this layout can fully fit side-by-side. If
-     * not, the content pane has the capability to slide back and forth. Note that the lock mode
-     * is not taken into account in this method. This method is typically used to determine
-     * whether the layout is showing two-pane or single-pane.
-     *
-     * @return true if both panes cannot fit side-by-side, and detail pane in this layout has
-     * the capability to slide back and forth.
-     */
-    public boolean isSlideable() {
-        return mCanSlide;
-    }
-
-    /**
-     * @param opened position to switch to
-     */
-    boolean slideTo(boolean opened) {
-        if (!mCanSlide) {
-            // Nothing to do.
-            return false;
-        }
-
-        View slideableView = mSlideableView;
-        isOpened = opened;
-        mFirstLayout = true;
-        requestLayout();
-        invalidate();
-        if (opened)
-            dispatchOnPanelOpened(slideableView);
-        else
-            dispatchOnPanelClosed(slideableView);
-        mPreservedOpenState = opened;
-        return true;
-    }
-
-    @Override
-    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
-        return new LayoutParams();
-    }
-
-    @Override
-    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
-        return p instanceof MarginLayoutParams
-                ? new LayoutParams((MarginLayoutParams) p)
-                : new LayoutParams(p);
-    }
-
-    @Override
-    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
-        return p instanceof LayoutParams && super.checkLayoutParams(p);
-    }
-
-    @Override
-    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
-        return new LayoutParams(getContext(), attrs);
-    }
-
-    @Override
-    protected Parcelable onSaveInstanceState() {
-        Parcelable superState = super.onSaveInstanceState();
-        SavedState ss = new SavedState(superState);
-        ss.isOpen = isSlideable() ? isOpen() : mPreservedOpenState;
-        return ss;
-    }
-
-    @Override
-    protected void onRestoreInstanceState(Parcelable state) {
-        if (!(state instanceof SavedState)) {
-            super.onRestoreInstanceState(state);
-            return;
-        }
-
-        SavedState ss = (SavedState) state;
-        super.onRestoreInstanceState(ss.getSuperState());
-
-        if (ss.isOpen) {
-            openPane();
-        } else {
-            closePane();
-        }
-        mPreservedOpenState = ss.isOpen;
-    }
-
-    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
-        private static final int[] ATTRS = new int[]{
-            android.R.attr.layout_weight
-        };
-
-        /**
-         * The weighted proportion of how much of the leftover space
-         * this child should consume after measurement.
-         */
-        public float weight = 0;
-
-        /**
-         * True if this pane is the slideable pane in the layout.
-         */
-        boolean slideable;
-
-        public LayoutParams() {
-            super(MATCH_PARENT, MATCH_PARENT);
-        }
-
-        public LayoutParams(int width, int height) {
-            super(width, height);
-        }
-
-        public LayoutParams(@NonNull android.view.ViewGroup.LayoutParams source) {
-            super(source);
-        }
-
-        public LayoutParams(@NonNull MarginLayoutParams source) {
-            super(source);
-        }
-
-        public LayoutParams(@NonNull LayoutParams source) {
-            super(source);
-            this.weight = source.weight;
-        }
-
-        public LayoutParams(@NonNull Context c, @Nullable AttributeSet attrs) {
-            super(c, attrs);
-
-            final TypedArray a = c.obtainStyledAttributes(attrs, ATTRS);
-            this.weight = a.getFloat(0, 0);
-            a.recycle();
-        }
-
-    }
-
-    static class SavedState extends AbsSavedState {
-        boolean isOpen;
-
-        SavedState(Parcelable superState) {
-            super(superState);
-        }
-
-        SavedState(Parcel in, ClassLoader loader) {
-            super(in, loader);
-            isOpen = in.readInt() != 0;
-        }
-
-        @Override
-        public void writeToParcel(Parcel out, int flags) {
-            super.writeToParcel(out, flags);
-            out.writeInt(isOpen ? 1 : 0);
-        }
-
-        public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() {
-            @Override
-            public SavedState createFromParcel(Parcel in, ClassLoader loader) {
-                return new SavedState(in, null);
-            }
-
-            @Override
-            public SavedState createFromParcel(Parcel in) {
-                return new SavedState(in, null);
-            }
-
-            @Override
-            public SavedState[] newArray(int size) {
-                return new SavedState[size];
-            }
-        };
-    }
-
-    class AccessibilityDelegate extends AccessibilityDelegateCompat {
-        private final Rect mTmpRect = new Rect();
-
-        @Override
-        public void onInitializeAccessibilityNodeInfo(@NonNull View host, @NonNull AccessibilityNodeInfoCompat info) {
-            final AccessibilityNodeInfoCompat superNode = AccessibilityNodeInfoCompat.obtain(info);
-            super.onInitializeAccessibilityNodeInfo(host, superNode);
-            copyNodeInfoNoChildren(info, superNode);
-            superNode.recycle();
-
-            info.setClassName(ACCESSIBILITY_CLASS_NAME);
-            info.setSource(host);
-
-            final ViewParent parent = ViewCompat.getParentForAccessibility(host);
-            if (parent instanceof View) {
-                info.setParent((View) parent);
-            }
-
-            // This is a best-approximation of addChildrenForAccessibility()
-            // that accounts for filtering.
-            final int childCount = getChildCount();
-            for (int i = 0; i < childCount; i++) {
-                final View child = getChildAt(i);
-                if (child.getVisibility() == View.VISIBLE) {
-                    // Force importance to "yes" since we can't read the value.
-                    ViewCompat.setImportantForAccessibility(
-                            child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
-                    info.addChild(child);
-                }
-            }
-        }
-
-        @Override
-        public void onInitializeAccessibilityEvent(@NonNull View host, @NonNull AccessibilityEvent event) {
-            super.onInitializeAccessibilityEvent(host, event);
-            event.setClassName(ACCESSIBILITY_CLASS_NAME);
-        }
-
-        /**
-         * This should really be in AccessibilityNodeInfoCompat, but there unfortunately
-         * seem to be a few elements that are not easily cloneable using the underlying API.
-         * Leave it private here as it's not general-purpose useful.
-         */
-        private void copyNodeInfoNoChildren(AccessibilityNodeInfoCompat dest,
-                AccessibilityNodeInfoCompat src) {
-            final Rect rect = mTmpRect;
-
-            src.getBoundsInScreen(rect);
-            dest.setBoundsInScreen(rect);
-
-            dest.setVisibleToUser(src.isVisibleToUser());
-            dest.setPackageName(src.getPackageName());
-            dest.setClassName(src.getClassName());
-            dest.setContentDescription(src.getContentDescription());
-
-            dest.setEnabled(src.isEnabled());
-            dest.setClickable(src.isClickable());
-            dest.setFocusable(src.isFocusable());
-            dest.setFocused(src.isFocused());
-            dest.setAccessibilityFocused(src.isAccessibilityFocused());
-            dest.setSelected(src.isSelected());
-            dest.setLongClickable(src.isLongClickable());
-
-            dest.addAction(src.getActions());
-
-            dest.setMovementGranularities(src.getMovementGranularities());
-        }
-    }
-
-    boolean isLayoutRtlSupport() {
-        return ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL;
-    }
-
-    /**
-     * @return A pair of rects define the position of the split, or {@null} if there is no split
-     */
-    private ArrayList<Rect> splitViewPositions() {
-        if (mFoldingFeature == null || !mFoldingFeature.isSeparating()) {
-            return null;
-        }
-
-        // Don't support horizontal fold in list-detail view layout
-        if (mFoldingFeature.getBounds().left == 0) {
-            return null;
-        }
-        // vertical split
-        if (mFoldingFeature.getBounds().top == 0) {
-            Rect splitPosition = getFoldBoundsInView(mFoldingFeature, this);
-            if (splitPosition == null) {
-                return null;
-            }
-            Rect leftRect = new Rect(getPaddingLeft(), getPaddingTop(),
-                    Math.max(getPaddingLeft(), splitPosition.left),
-                    getHeight() - getPaddingBottom());
-            int rightBound = getWidth() - getPaddingRight();
-            Rect rightRect = new Rect(Math.min(rightBound, splitPosition.right),
-                    getPaddingTop(), rightBound, getHeight() - getPaddingBottom());
-            return new ArrayList<>(Arrays.asList(leftRect, rightRect));
-        }
-        return null;
-    }
-
-    private static Rect getFoldBoundsInView(@NonNull FoldingFeature foldingFeature, View view) {
-        int[] viewLocationInWindow = new int[2];
-        view.getLocationInWindow(viewLocationInWindow);
-
-        Rect viewRect = new Rect(viewLocationInWindow[0], viewLocationInWindow[1],
-                viewLocationInWindow[0] + view.getWidth(),
-                viewLocationInWindow[1] + view.getWidth());
-        Rect foldRectInView = new Rect(foldingFeature.getBounds());
-        // Translate coordinate space of split from window coordinate space to current view
-        // position in window
-        boolean intersects = foldRectInView.intersect(viewRect);
-        // Check if the split is overlapped with the view
-        if ((foldRectInView.width() == 0 && foldRectInView.height() == 0) || !intersects) {
-            return null;
-        }
-        foldRectInView.offset(-viewLocationInWindow[0], -viewLocationInWindow[1]);
-        return foldRectInView;
-    }
-
-    @Nullable
-    private static Activity getActivityOrNull(Context context) {
-        Context iterator = context;
-        while (iterator instanceof ContextWrapper) {
-            if (iterator instanceof Activity) {
-                return (Activity) iterator;
-            }
-            iterator = ((ContextWrapper) iterator).getBaseContext();
-        }
-        return null;
-    }
-}
diff --git a/jami-android/app/src/main/java/cx/ring/views/twopane/TwoPaneLayout.kt b/jami-android/app/src/main/java/cx/ring/views/twopane/TwoPaneLayout.kt
new file mode 100644
index 0000000000000000000000000000000000000000..68a197b92d08ce22c4ec785999d43a9cadf23667
--- /dev/null
+++ b/jami-android/app/src/main/java/cx/ring/views/twopane/TwoPaneLayout.kt
@@ -0,0 +1,767 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package cx.ring.views.twopane
+
+import android.app.Activity
+import android.content.Context
+import android.content.ContextWrapper
+import android.graphics.Rect
+import android.os.Parcel
+import android.os.Parcelable
+import android.os.Parcelable.ClassLoaderCreator
+import android.util.AttributeSet
+import android.util.Log
+import android.view.View
+import android.view.ViewGroup
+import android.view.accessibility.AccessibilityEvent
+import androidx.core.content.ContextCompat
+import androidx.core.view.AccessibilityDelegateCompat
+import androidx.core.view.ViewCompat
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
+import androidx.core.view.animation.PathInterpolatorCompat
+import androidx.customview.view.AbsSavedState
+import androidx.customview.widget.Openable
+import androidx.transition.ChangeBounds
+import androidx.transition.Transition
+import androidx.transition.TransitionManager
+import androidx.window.layout.FoldingFeature
+import androidx.window.layout.FoldingFeature.Orientation.Companion.VERTICAL
+import androidx.window.layout.WindowInfoTracker
+import java.util.concurrent.CopyOnWriteArrayList
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * TwoPaneLayout provides a horizontal, multi-pane layout for use at the top level
+ * of a UI. A left (or start) pane is treated as a content list or browser, subordinate to a
+ * primary detail view for displaying content.
+ *
+ * Each of child views is expand out to fill the available width in
+ * the TwoPaneLayout.
+ *
+ * Thanks to this behavior, TwoPaneLayout may be suitable for creating layouts
+ * that can smoothly adapt across many different screen sizes, expanding out fully on larger
+ * screens and collapsing on smaller screens.
+ *
+ * Like [LinearLayout][android.widget.LinearLayout], TwoPaneLayout supports
+ * the use of the layout parameter `layout_weight` on child views to determine
+ * how to divide leftover space after measurement is complete. It is only relevant for width.
+ * When views do not overlap weight behaves as it does in a LinearLayout.
+ */
+class TwoPaneLayout @JvmOverloads constructor(
+    context: Context, attrs: AttributeSet? = null,
+    defStyle: Int = 0
+) : ViewGroup(context, attrs, defStyle), Openable {
+    /**
+     * Check if both the list and detail view panes in this layout can fully fit side-by-side. If
+     * not, the content pane has the capability to slide back and forth. Note that the lock mode
+     * is not taken into account in this method. This method is typically used to determine
+     * whether the layout is showing two-pane or single-pane.
+     *
+     * @return true if both panes cannot fit side-by-side, and detail pane in this layout has
+     * the capability to slide back and forth.
+     */
+    /**
+     * True if a panel can slide with the current measurements
+     */
+    var isSlideable = false
+        private set
+
+    /**
+     * The child view that can slide, if any.
+     */
+    private var mSlideableView: View? = null
+
+    /**
+     * How far the panel is offset from its usual position.
+     * range [0, 1] where 0 = open, 1 = closed.
+     */
+    private var isOpened = false
+    private val mPanelListeners: MutableList<PanelListener> = CopyOnWriteArrayList()
+
+    private val tmpRect = Rect()
+    private val tmpRect2 = Rect()
+    private val foldBoundsCalculator = FoldBoundsCalculator()
+
+    /**
+     * Stores whether or not the pane was open the last time it was slideable.
+     * If open/close operations are invoked this state is modified. Used by
+     * instance state save/restore.
+     */
+    private var mPreservedOpenState = false
+    private var mFirstLayout = true
+    private var foldingFeature: FoldingFeature? = null
+        set(value) {
+            if (value != field) {
+                field = value
+                val changeBounds: Transition = ChangeBounds()
+                changeBounds.setDuration(300L)
+                changeBounds.setInterpolator(PathInterpolatorCompat.create(0.2f, 0f, 0f, 1f))
+                TransitionManager.beginDelayedTransition(this@TwoPaneLayout, changeBounds)
+                requestLayout()
+            }
+        }
+
+    /**
+     * Listener for monitoring events about sliding panes.
+     */
+    interface PanelListener {
+        /**
+         * Called when a detail view becomes slid completely open.
+         *
+         * @param panel The detail view that was slid to an open position
+         */
+        fun onPanelOpened(panel: View)
+
+        /**
+         * Called when a detail view becomes slid completely closed.
+         *
+         * @param panel The detail view that was slid to a closed position
+         */
+        fun onPanelClosed(panel: View)
+    }
+
+    private val mFoldingFeatureObserver: FoldingFeatureObserver?
+    init {
+        ViewCompat.setAccessibilityDelegate(this, AccessibilityDelegate())
+        ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES)
+        val repo = WindowInfoTracker.getOrCreate(context)
+        val mainExecutor = ContextCompat.getMainExecutor(context)
+        mFoldingFeatureObserver = FoldingFeatureObserver(repo, mainExecutor)
+        mFoldingFeatureObserver.setOnFoldingFeatureChangeListener(object : FoldingFeatureObserver.OnFoldingFeatureChangeListener {
+            override fun onFoldingFeatureChange(foldingFeature: FoldingFeature) {
+                this@TwoPaneLayout.foldingFeature = foldingFeature
+            }
+        })
+    }
+
+    /**
+     * Adds the specified listener to the list of listeners that will be notified of
+     * panel slide events.
+     *
+     * @param listener Listener to notify when panel slide events occur.
+     * @see .removePanelListener
+     */
+    fun addPanelListener(listener: PanelListener) {
+        mPanelListeners.add(listener)
+    }
+
+    /**
+     * Removes the specified listener from the list of listeners that will be notified of
+     * panel slide events.
+     *
+     * @param listener Listener to remove from being notified of panel slide events
+     * @see .addPanelListener
+     */
+    fun removePanelListener(listener: PanelListener) {
+        mPanelListeners.remove(listener)
+    }
+
+    fun dispatchOnPanelOpened(panel: View) {
+        for (listener in mPanelListeners) {
+            listener.onPanelOpened(panel)
+        }
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED)
+    }
+
+    fun dispatchOnPanelClosed(panel: View) {
+        for (listener in mPanelListeners) {
+            listener.onPanelClosed(panel)
+        }
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED)
+    }
+
+    override fun onAttachedToWindow() {
+        super.onAttachedToWindow()
+        mFirstLayout = true
+        if (mFoldingFeatureObserver != null) {
+            val activity = getActivityOrNull(context)
+            if (activity != null) {
+                mFoldingFeatureObserver.registerLayoutStateChangeCallback(activity)
+            }
+        }
+    }
+
+    override fun onDetachedFromWindow() {
+        super.onDetachedFromWindow()
+        mFirstLayout = true
+        mFoldingFeatureObserver?.unregisterLayoutStateChangeCallback()
+    }
+
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
+        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
+        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
+        val heightSize = MeasureSpec.getSize(heightMeasureSpec)
+        var layoutHeight = 0
+        var maxLayoutHeight = 0
+        when (heightMode) {
+            MeasureSpec.EXACTLY -> {
+                maxLayoutHeight = heightSize - paddingTop - paddingBottom
+                layoutHeight = maxLayoutHeight
+            }
+            MeasureSpec.AT_MOST -> maxLayoutHeight = heightSize - paddingTop - paddingBottom
+        }
+        var weightSum = 0f
+        var canSlide = false
+        val widthAvailable = max(widthSize - getPaddingLeft() - getPaddingRight(), 0)
+        var widthRemaining = widthAvailable
+        val childCount = childCount
+        if (childCount > 2) {
+            Log.e(TAG, "onMeasure: More than two child views are not supported.")
+        }
+
+        // We'll find the current one below.
+        mSlideableView = null
+
+        // First pass. Measure based on child LayoutParams width/height.
+        // Weight will incur a second pass.
+        for (i in 0 until childCount) {
+            val child = getChildAt(i)
+            val lp = child.layoutParams as LayoutParams
+            if (child.visibility == GONE) {
+                continue
+            }
+            if (lp.weight > 0) {
+                weightSum += lp.weight
+
+                // If we have no width, weight is the only contributor to the final size.
+                // Measure this view on the weight pass only.
+                if (lp.width == 0) continue
+            }
+            var childWidthSpec: Int
+            val horizontalMargin = lp.leftMargin + lp.rightMargin
+            val childWidthSize = max(widthAvailable - horizontalMargin, 0)
+            // When the parent width spec is UNSPECIFIED, measure each of child to get its
+            // desired width.
+            childWidthSpec = when (lp.width) {
+                ViewGroup.LayoutParams.WRAP_CONTENT -> MeasureSpec.makeMeasureSpec(childWidthSize,
+                        if (widthMode == MeasureSpec.UNSPECIFIED) widthMode else MeasureSpec.AT_MOST)
+                ViewGroup.LayoutParams.MATCH_PARENT -> MeasureSpec.makeMeasureSpec(childWidthSize, widthMode)
+                else -> MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY)
+            }
+            val childHeightSpec = getChildMeasureSpec(heightMeasureSpec,
+                paddingTop + paddingBottom, lp.height
+            )
+            child.measure(childWidthSpec, childHeightSpec)
+            val childWidth = child.measuredWidth
+            val childHeight = child.measuredHeight
+            if (childHeight > layoutHeight) {
+                if (heightMode == MeasureSpec.AT_MOST) {
+                    layoutHeight = min(childHeight, maxLayoutHeight)
+                } else if (heightMode == MeasureSpec.UNSPECIFIED) {
+                    layoutHeight = childHeight
+                }
+            }
+            widthRemaining -= childWidth
+            // Skip first child (list pane), the list pane is always a non-sliding pane.
+            if (i == 0) {
+                continue
+            }
+            lp.slideable = widthRemaining < 0
+            canSlide = canSlide or lp.slideable
+            if (lp.slideable) {
+                mSlideableView = child
+            }
+        }
+        // Second pass. Resolve weight.
+        // Child views overlap when the combined width of child views cannot fit into the
+        // available width. Each of child views is sized to fill all available space. If there is
+        // no overlap, distribute the extra width proportionally to weight.
+        if (canSlide || weightSum > 0) {
+            for (i in 0 until childCount) {
+                val child = getChildAt(i)
+                if (child.visibility == GONE) {
+                    continue
+                }
+                val lp = child.layoutParams as LayoutParams
+                val skippedFirstPass = lp.width == 0 && lp.weight > 0
+                val measuredWidth = if (skippedFirstPass) 0 else child.measuredWidth
+                var newWidth = measuredWidth
+                var childWidthSpec = 0
+                if (canSlide) {
+                    // Child view consumes available space if the combined width cannot fit into
+                    // the layout available width.
+                    val horizontalMargin = lp.leftMargin + lp.rightMargin
+                    newWidth = widthAvailable - horizontalMargin
+                    childWidthSpec = MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY)
+                } else if (lp.weight > 0) {
+                    // Distribute the extra width proportionally similar to LinearLayout
+                    val widthToDistribute = max(0, widthRemaining)
+                    val addedWidth = (lp.weight * widthToDistribute / weightSum).toInt()
+                    newWidth = measuredWidth + addedWidth
+                    childWidthSpec = MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY)
+                }
+                val childHeightSpec = measureChildHeight(
+                    child, heightMeasureSpec,
+                    paddingTop + paddingBottom
+                )
+                if (measuredWidth != newWidth) {
+                    child.measure(childWidthSpec, childHeightSpec)
+                    val childHeight = child.measuredHeight
+                    if (childHeight > layoutHeight) {
+                        if (heightMode == MeasureSpec.AT_MOST) {
+                            layoutHeight = min(childHeight, maxLayoutHeight)
+                        } else if (heightMode == MeasureSpec.UNSPECIFIED) {
+                            layoutHeight = childHeight
+                        }
+                    }
+                }
+            }
+        }
+
+        // At this point, all child views have been measured. Calculate the device fold position
+        // in the view. Update the split position to where the fold when it exists.
+        val leftSplitBounds = tmpRect
+        val rightSplitBounds = tmpRect2
+        val hasFold = foldBoundsCalculator.splitViewPositions(foldingFeature, this, leftSplitBounds, rightSplitBounds)
+        if (hasFold && !canSlide) {
+            for (i in 0 until childCount) {
+                val child = getChildAt(i)
+                if (child.visibility == GONE) {
+                    continue
+                }
+                val splitView = if (i == 0) leftSplitBounds else rightSplitBounds
+                val lp = child.layoutParams as LayoutParams
+
+                // If child view cannot fit in the separating view, expand the child view to fill
+                // available space.
+                val horizontalMargin = lp.leftMargin + lp.rightMargin
+                val childHeightSpec = MeasureSpec.makeMeasureSpec(child.measuredHeight, MeasureSpec.EXACTLY)
+                var childWidthSpec = MeasureSpec.makeMeasureSpec(splitView.width(), MeasureSpec.AT_MOST)
+                child.measure(childWidthSpec, childHeightSpec)
+                if ((getMinimumWidth(child) != 0 && splitView.width() < getMinimumWidth(child))) {
+                    childWidthSpec = MeasureSpec.makeMeasureSpec(widthAvailable - horizontalMargin, MeasureSpec.EXACTLY)
+                    child.measure(childWidthSpec, childHeightSpec)
+                    // Skip first child (list pane), the list pane is always a non-sliding pane.
+                    if (i == 0) {
+                        continue
+                    }
+                    lp.slideable = true
+                    canSlide = lp.slideable
+                    mSlideableView = child
+                } else {
+                    childWidthSpec = MeasureSpec.makeMeasureSpec(splitView.width(), MeasureSpec.EXACTLY)
+                    child.measure(childWidthSpec, childHeightSpec)
+                }
+            }
+        }
+        val measuredHeight = layoutHeight + paddingTop + paddingBottom
+        setMeasuredDimension(widthSize, measuredHeight)
+        isSlideable = canSlide
+    }
+
+    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+        val isLayoutRtl = isLayoutRtlSupport
+        val width = r - l
+        val paddingStart = if (isLayoutRtl) getPaddingRight() else getPaddingLeft()
+        val paddingEnd = if (isLayoutRtl) getPaddingLeft() else getPaddingRight()
+        val paddingTop = paddingTop
+        val childCount = childCount
+        var xStart = paddingStart
+        var nextXStart = xStart
+        if (mFirstLayout) {
+            isOpened = isSlideable && mPreservedOpenState
+        }
+        for (i in 0 until childCount) {
+            val child = getChildAt(i)
+            if (child.visibility == GONE) {
+                continue
+            }
+            val lp = child.layoutParams as LayoutParams
+            val childWidth = child.measuredWidth
+            val offset = 0
+            if (lp.slideable) {
+                val margin = lp.leftMargin + lp.rightMargin
+                val range = (min(nextXStart, (width - paddingEnd)) - xStart - margin)
+                val lpMargin = if (isLayoutRtl) lp.rightMargin else lp.leftMargin
+                val pos = if (isOpened || foldingFeature == null) 0 else range
+                xStart += pos + lpMargin
+            } else {
+                xStart = nextXStart
+            }
+            val childRight: Int
+            val childLeft: Int
+            if (isLayoutRtl) {
+                childRight = width - xStart + offset
+                childLeft = childRight - childWidth
+            } else {
+                childLeft = xStart - offset
+                childRight = childLeft + childWidth
+            }
+            val childBottom = paddingTop + child.measuredHeight
+            child.layout(childLeft, paddingTop, childRight, childBottom)
+
+            // If a folding feature separates the content, we use its width as the extra
+            // offset for the next child, in order to avoid rendering the content under it.
+            var nextXOffset = 0
+            if (foldingFeature != null && foldingFeature!!.orientation == VERTICAL && foldingFeature!!.isSeparating) {
+                nextXOffset = foldingFeature!!.bounds.width()
+            }
+            nextXStart += child.width + abs(nextXOffset)
+        }
+        mFirstLayout = false
+    }
+
+    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+        super.onSizeChanged(w, h, oldw, oldh)
+        // Recalculate sliding panes and their details
+        if (w != oldw) {
+            mFirstLayout = true
+        }
+    }
+
+    override fun requestChildFocus(child: View, focused: View) {
+        super.requestChildFocus(child, focused)
+        if (!isInTouchMode() && !isSlideable) {
+            mPreservedOpenState = child === mSlideableView
+        }
+    }
+
+    /**
+     * Close the detail view if it is currently slideable. If first layout
+     * has already completed this will animate.
+     *
+     * @return true if the pane was slideable and is now closed/in the process of closing
+     */
+    fun closePane(): Boolean {
+        if (!isSlideable) {
+            mPreservedOpenState = false
+        }
+        if (mFirstLayout || slideTo(false)) {
+            mPreservedOpenState = false
+            return true
+        }
+        return false
+    }
+
+    /**
+     * Open the detail view if it is currently slideable. If first layout
+     * has already completed this will animate.
+     *
+     * @return true if the pane was slideable and is now open/in the process of opening
+     */
+    fun openPane(): Boolean {
+        if (!isSlideable) {
+            mPreservedOpenState = true
+        }
+        if (mFirstLayout || slideTo(true)) {
+            mPreservedOpenState = true
+            return true
+        }
+        return false
+    }
+
+    /**
+     * Open the detail view if it is currently slideable. If first layout
+     * has already completed this will animate.
+     */
+    override fun open() {
+        openPane()
+    }
+
+    /**
+     * Close the detail view if it is currently slideable. If first layout
+     * has already completed this will animate.
+     */
+    override fun close() {
+        closePane()
+    }
+
+    /**
+     * Check if the detail view is completely open. It can be open either because the slider
+     * itself is open revealing the detail view, or if all content fits without sliding.
+     *
+     * @return true if the detail view is completely open
+     */
+    override fun isOpen(): Boolean = !isSlideable || isOpened
+
+    /**
+     * @param opened position to switch to
+     */
+    fun slideTo(opened: Boolean): Boolean {
+        if (!isSlideable) {
+            // Nothing to do.
+            return false
+        }
+        val slideableView = mSlideableView
+        isOpened = opened
+        mFirstLayout = true
+        requestLayout()
+        invalidate()
+        if (opened) dispatchOnPanelOpened(slideableView!!) else dispatchOnPanelClosed(slideableView!!)
+        mPreservedOpenState = opened
+        return true
+    }
+
+    override fun generateDefaultLayoutParams(): ViewGroup.LayoutParams = LayoutParams()
+
+    override fun generateLayoutParams(p: ViewGroup.LayoutParams): ViewGroup.LayoutParams =
+        if (p is MarginLayoutParams) LayoutParams(p) else LayoutParams(p)
+
+    override fun checkLayoutParams(p: ViewGroup.LayoutParams): Boolean =
+        p is LayoutParams && super.checkLayoutParams(p)
+
+    override fun generateLayoutParams(attrs: AttributeSet): ViewGroup.LayoutParams = LayoutParams(context, attrs)
+
+    override fun onSaveInstanceState(): Parcelable {
+        val superState = super.onSaveInstanceState()
+        val ss = SavedState(superState)
+        ss.isOpen = if (isSlideable) isOpen else mPreservedOpenState
+        return ss
+    }
+
+    override fun onRestoreInstanceState(state: Parcelable) {
+        if (state !is SavedState) {
+            super.onRestoreInstanceState(state)
+            return
+        }
+        super.onRestoreInstanceState(state.superState)
+        if (state.isOpen) {
+            openPane()
+        } else {
+            closePane()
+        }
+        mPreservedOpenState = state.isOpen
+    }
+
+    class LayoutParams : MarginLayoutParams {
+        /**
+         * The weighted proportion of how much of the leftover space
+         * this child should consume after measurement.
+         */
+        var weight = 0f
+
+        /**
+         * True if this pane is the slideable pane in the layout.
+         */
+        var slideable = false
+
+        constructor() : super(MATCH_PARENT, MATCH_PARENT)
+        constructor(width: Int, height: Int) : super(width, height)
+        constructor(source: ViewGroup.LayoutParams) : super(source)
+        constructor(source: MarginLayoutParams) : super(source)
+        constructor(source: LayoutParams) : super(source) {
+            weight = source.weight
+        }
+
+        constructor(c: Context, attrs: AttributeSet?) : super(c, attrs) {
+            val a = c.obtainStyledAttributes(attrs, ATTRS)
+            weight = a.getFloat(0, 0f)
+            a.recycle()
+        }
+
+        companion object {
+            private val ATTRS = intArrayOf(
+                android.R.attr.layout_weight
+            )
+        }
+    }
+
+    internal class SavedState : AbsSavedState {
+        var isOpen = false
+
+        constructor(superState: Parcelable?) : super(superState!!)
+        constructor(i: Parcel, loader: ClassLoader?) : super(i, loader) {
+            isOpen = i.readInt() != 0
+        }
+
+        override fun writeToParcel(out: Parcel, flags: Int) {
+            super.writeToParcel(out, flags)
+            out.writeInt(if (isOpen) 1 else 0)
+        }
+
+        companion object {
+            @JvmField
+            val CREATOR: Parcelable.Creator<SavedState> = object : ClassLoaderCreator<SavedState> {
+                override fun createFromParcel(i: Parcel, loader: ClassLoader): SavedState = SavedState(i, null)
+                override fun createFromParcel(i: Parcel): SavedState = SavedState(i, null)
+                override fun newArray(size: Int): Array<SavedState?> = arrayOfNulls(size)
+            }
+        }
+    }
+
+    internal inner class AccessibilityDelegate : AccessibilityDelegateCompat() {
+        private val mTmpRect = Rect()
+        override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
+            val superNode = AccessibilityNodeInfoCompat.obtain(info)
+            super.onInitializeAccessibilityNodeInfo(host, superNode)
+            copyNodeInfoNoChildren(info, superNode)
+            @Suppress("Deprecation")
+            superNode.recycle()
+            info.className = ACCESSIBILITY_CLASS_NAME
+            info.setSource(host)
+            val parent = ViewCompat.getParentForAccessibility(host)
+            if (parent is View) {
+                info.setParent(parent as View?)
+            }
+
+            // This is a best-approximation of addChildrenForAccessibility()
+            // that accounts for filtering.
+            val childCount = childCount
+            for (i in 0 until childCount) {
+                val child = getChildAt(i)
+                if (child.visibility == VISIBLE) {
+                    // Force importance to "yes" since we can't read the value.
+                    ViewCompat.setImportantForAccessibility(child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES)
+                    info.addChild(child)
+                }
+            }
+        }
+
+        override fun onInitializeAccessibilityEvent(host: View, event: AccessibilityEvent) {
+            super.onInitializeAccessibilityEvent(host, event)
+            event.setClassName(ACCESSIBILITY_CLASS_NAME)
+        }
+
+        /**
+         * This should really be in AccessibilityNodeInfoCompat, but there unfortunately
+         * seem to be a few elements that are not easily cloneable using the underlying API.
+         * Leave it private here as it's not general-purpose useful.
+         */
+        private fun copyNodeInfoNoChildren(dest: AccessibilityNodeInfoCompat, src: AccessibilityNodeInfoCompat) {
+            val rect = mTmpRect
+            src.getBoundsInScreen(rect)
+            dest.setBoundsInScreen(rect)
+            dest.isVisibleToUser = src.isVisibleToUser
+            dest.packageName = src.packageName
+            dest.className = src.className
+            dest.contentDescription = src.contentDescription
+            dest.isEnabled = src.isEnabled
+            dest.isClickable = src.isClickable
+            dest.isFocusable = src.isFocusable
+            dest.isFocused = src.isFocused
+            dest.isAccessibilityFocused = src.isAccessibilityFocused
+            dest.isSelected = src.isSelected
+            dest.isLongClickable = src.isLongClickable
+            @Suppress("Deprecation")
+            dest.addAction(src.actions)
+            dest.movementGranularities = src.movementGranularities
+        }
+    }
+
+    private val isLayoutRtlSupport: Boolean
+        get() = ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL
+
+
+    /**
+     * Utility for calculating layout positioning of child views relative to a [FoldingFeature].
+     * This class is not thread-safe.
+     */
+    private class FoldBoundsCalculator {
+        private val tmpIntArray = IntArray(2)
+        private val splitViewPositionsTmpRect = Rect()
+        private val getFoldBoundsInViewTmpRect = Rect()
+        /**
+         * Returns `true` if there is a split and [outLeftRect] and [outRightRect] contain the split
+         * positions; false if there is not a compatible split available, [outLeftRect] and
+         * [outRightRect] will remain unmodified.
+         */
+        fun splitViewPositions(
+            foldingFeature: FoldingFeature?,
+            parentView: View,
+            outLeftRect: Rect,
+            outRightRect: Rect,
+        ): Boolean {
+            if (foldingFeature == null) return false
+            if (!foldingFeature.isSeparating) return false
+            // Don't support horizontal fold in list-detail view layout
+            if (foldingFeature.bounds.left == 0) return false
+            // vertical split
+            val splitPosition = splitViewPositionsTmpRect
+            if (foldingFeature.bounds.top == 0 &&
+                getFoldBoundsInView(foldingFeature, parentView, splitPosition)
+            ) {
+                outLeftRect.set(
+                    parentView.paddingLeft,
+                    parentView.paddingTop,
+                    max(parentView.paddingLeft, splitPosition.left),
+                    parentView.height - parentView.paddingBottom
+                )
+                val rightBound = parentView.width - parentView.paddingRight
+                outRightRect.set(
+                    min(rightBound, splitPosition.right),
+                    parentView.paddingTop,
+                    rightBound,
+                    parentView.height - parentView.paddingBottom
+                )
+                return true
+            }
+            return false
+        }
+        /**
+         * Returns `true` if [foldingFeature] overlaps with [view] and writes the bounds to [outRect].
+         */
+        private fun getFoldBoundsInView(
+            foldingFeature: FoldingFeature,
+            view: View,
+            outRect: Rect
+        ): Boolean {
+            val viewLocationInWindow = tmpIntArray
+            view.getLocationInWindow(viewLocationInWindow)
+            val x = viewLocationInWindow[0]
+            val y = viewLocationInWindow[1]
+            val viewRect = getFoldBoundsInViewTmpRect.apply {
+                set(x, y, x + view.width, y + view.width)
+            }
+            val foldRectInView = outRect.apply { set(foldingFeature.bounds) }
+            // Translate coordinate space of split from window coordinate space to current view
+            // position in window
+            val intersects = foldRectInView.intersect(viewRect)
+            // Check if the split is overlapped with the view
+            if (foldRectInView.width() == 0 && foldRectInView.height() == 0 || !intersects) {
+                return false
+            }
+            foldRectInView.offset(-x, -y)
+            return true
+        }
+    }
+
+    companion object {
+        private const val TAG = "TwoPaneLayout"
+
+        /** Class name may be obfuscated by Proguard. Hardcode the string for accessibility usage.  */
+        private const val ACCESSIBILITY_CLASS_NAME = "cx.ring.views.twopane.TwoPaneLayout"
+        private fun getMinimumWidth(child: View): Int {
+            return ViewCompat.getMinimumWidth(child)
+        }
+
+        private fun measureChildHeight(child: View, spec: Int, padding: Int): Int {
+            val lp = child.layoutParams as LayoutParams
+            val childHeightSpec: Int
+            val skippedFirstPass = lp.width == 0 && lp.weight > 0
+            childHeightSpec = if (skippedFirstPass) {
+                // This was skipped the first time; figure out a real height spec.
+                getChildMeasureSpec(spec, padding, lp.height)
+            } else {
+                MeasureSpec.makeMeasureSpec(child.measuredHeight, MeasureSpec.EXACTLY)
+            }
+            return childHeightSpec
+        }
+
+        private fun getActivityOrNull(context: Context): Activity? {
+            var iterator: Context? = context
+            while (iterator is ContextWrapper) {
+                if (iterator is Activity) {
+                    return iterator
+                }
+                iterator = iterator.baseContext
+            }
+            return null
+        }
+    }
+}