1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
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
14 * limitations under the License.
15 */
16
17package com.android.systemui.stackdivider;
18
19import android.content.res.Configuration;
20import android.os.RemoteException;
21import android.view.IDockedStackListener;
22import android.view.LayoutInflater;
23import android.view.View;
24
25import com.android.systemui.R;
26import com.android.systemui.SystemUI;
27import com.android.systemui.recents.Recents;
28import com.android.systemui.recents.events.EventBus;
29import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
30import com.android.systemui.recents.misc.SystemServicesProxy;
31
32import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
33import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
34
35import java.io.FileDescriptor;
36import java.io.PrintWriter;
37
38/**
39 * Controls the docked stack divider.
40 */
41public class Divider extends SystemUI {
42    private DividerWindowManager mWindowManager;
43    private DividerView mView;
44    private final DividerState mDividerState = new DividerState();
45    private DockDividerVisibilityListener mDockDividerVisibilityListener;
46    private boolean mVisible = false;
47    private boolean mMinimized = false;
48    private boolean mAdjustedForIme = false;
49    private boolean mHomeStackResizable = false;
50    private ForcedResizableInfoActivityController mForcedResizableController;
51
52    @Override
53    public void start() {
54        mWindowManager = new DividerWindowManager(mContext);
55        update(mContext.getResources().getConfiguration());
56        putComponent(Divider.class, this);
57        mDockDividerVisibilityListener = new DockDividerVisibilityListener();
58        SystemServicesProxy ssp = Recents.getSystemServices();
59        ssp.registerDockedStackListener(mDockDividerVisibilityListener);
60        mForcedResizableController = new ForcedResizableInfoActivityController(mContext);
61        EventBus.getDefault().register(this);
62    }
63
64    @Override
65    protected void onConfigurationChanged(Configuration newConfig) {
66        super.onConfigurationChanged(newConfig);
67        update(newConfig);
68    }
69
70    public DividerView getView() {
71        return mView;
72    }
73
74    public boolean isMinimized() {
75        return mMinimized;
76    }
77
78    public boolean isHomeStackResizable() {
79        return mHomeStackResizable;
80    }
81
82    private void addDivider(Configuration configuration) {
83        mView = (DividerView)
84                LayoutInflater.from(mContext).inflate(R.layout.docked_stack_divider, null);
85        mView.injectDependencies(mWindowManager, mDividerState);
86        mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE);
87        mView.setMinimizedDockStack(mMinimized, mHomeStackResizable);
88        final int size = mContext.getResources().getDimensionPixelSize(
89                com.android.internal.R.dimen.docked_stack_divider_thickness);
90        final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
91        final int width = landscape ? size : MATCH_PARENT;
92        final int height = landscape ? MATCH_PARENT : size;
93        mWindowManager.add(mView, width, height);
94    }
95
96    private void removeDivider() {
97        if (mView != null) {
98            mView.onDividerRemoved();
99        }
100        mWindowManager.remove();
101    }
102
103    private void update(Configuration configuration) {
104        removeDivider();
105        addDivider(configuration);
106        if (mMinimized) {
107            mView.setMinimizedDockStack(true, mHomeStackResizable);
108            updateTouchable();
109        }
110    }
111
112    private void updateVisibility(final boolean visible) {
113        mView.post(new Runnable() {
114            @Override
115            public void run() {
116                if (mVisible != visible) {
117                    mVisible = visible;
118                    mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
119
120                    // Update state because animations won't finish.
121                    mView.setMinimizedDockStack(mMinimized, mHomeStackResizable);
122                }
123            }
124        });
125    }
126
127    private void updateMinimizedDockedStack(final boolean minimized, final long animDuration,
128            final boolean isHomeStackResizable) {
129        mView.post(new Runnable() {
130            @Override
131            public void run() {
132                mHomeStackResizable = isHomeStackResizable;
133                if (mMinimized != minimized) {
134                    mMinimized = minimized;
135                    updateTouchable();
136                    if (animDuration > 0) {
137                        mView.setMinimizedDockStack(minimized, animDuration, isHomeStackResizable);
138                    } else {
139                        mView.setMinimizedDockStack(minimized, isHomeStackResizable);
140                    }
141                }
142            }
143        });
144    }
145
146    private void notifyDockedStackExistsChanged(final boolean exists) {
147        mView.post(new Runnable() {
148            @Override
149            public void run() {
150                mForcedResizableController.notifyDockedStackExistsChanged(exists);
151            }
152        });
153    }
154
155    private void updateTouchable() {
156        mWindowManager.setTouchable((mHomeStackResizable || !mMinimized) && !mAdjustedForIme);
157    }
158
159    /**
160     * Workaround for b/62528361, at the time RecentsDrawnEvent is sent, it may happen before a
161     * configuration change to the Divider, and internally, the event will be posted to the
162     * subscriber, or DividerView, which has been removed and prevented from resizing. Instead,
163     * register the event handler here and proxy the event to the current DividerView.
164     */
165    public final void onBusEvent(RecentsDrawnEvent drawnEvent) {
166        if (mView != null) {
167            mView.onRecentsDrawn();
168        }
169    }
170
171    @Override
172    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
173        pw.print("  mVisible="); pw.println(mVisible);
174        pw.print("  mMinimized="); pw.println(mMinimized);
175        pw.print("  mAdjustedForIme="); pw.println(mAdjustedForIme);
176    }
177
178    class DockDividerVisibilityListener extends IDockedStackListener.Stub {
179
180        @Override
181        public void onDividerVisibilityChanged(boolean visible) throws RemoteException {
182            updateVisibility(visible);
183        }
184
185        @Override
186        public void onDockedStackExistsChanged(boolean exists) throws RemoteException {
187            notifyDockedStackExistsChanged(exists);
188        }
189
190        @Override
191        public void onDockedStackMinimizedChanged(boolean minimized, long animDuration,
192                boolean isHomeStackResizable) throws RemoteException {
193            mHomeStackResizable = isHomeStackResizable;
194            updateMinimizedDockedStack(minimized, animDuration, isHomeStackResizable);
195        }
196
197        @Override
198        public void onAdjustedForImeChanged(boolean adjustedForIme, long animDuration)
199                throws RemoteException {
200            mView.post(() -> {
201                if (mAdjustedForIme != adjustedForIme) {
202                    mAdjustedForIme = adjustedForIme;
203                    updateTouchable();
204                    if (!mMinimized) {
205                        if (animDuration > 0) {
206                            mView.setAdjustedForIme(adjustedForIme, animDuration);
207                        } else {
208                            mView.setAdjustedForIme(adjustedForIme);
209                        }
210                    }
211                }
212            });
213        }
214
215        @Override
216        public void onDockSideChanged(final int newDockSide) throws RemoteException {
217            mView.post(() -> mView.notifyDockSideChanged(newDockSide));
218        }
219    }
220}
221