1/*
2 * Copyright (C) 2014 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.inputmethod.keyboard.internal;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.content.Context;
22import android.view.View;
23import android.view.ViewGroup;
24
25import com.android.inputmethod.keyboard.Key;
26import com.android.inputmethod.latin.common.CoordinateUtils;
27import com.android.inputmethod.latin.utils.ViewLayoutUtils;
28
29import java.util.ArrayDeque;
30import java.util.HashMap;
31
32/**
33 * This class controls pop up key previews. This class decides:
34 * - what kind of key previews should be shown.
35 * - where key previews should be placed.
36 * - how key previews should be shown and dismissed.
37 */
38public final class KeyPreviewChoreographer {
39    // Free {@link KeyPreviewView} pool that can be used for key preview.
40    private final ArrayDeque<KeyPreviewView> mFreeKeyPreviewViews = new ArrayDeque<>();
41    // Map from {@link Key} to {@link KeyPreviewView} that is currently being displayed as key
42    // preview.
43    private final HashMap<Key,KeyPreviewView> mShowingKeyPreviewViews = new HashMap<>();
44
45    private final KeyPreviewDrawParams mParams;
46
47    public KeyPreviewChoreographer(final KeyPreviewDrawParams params) {
48        mParams = params;
49    }
50
51    public KeyPreviewView getKeyPreviewView(final Key key, final ViewGroup placerView) {
52        KeyPreviewView keyPreviewView = mShowingKeyPreviewViews.remove(key);
53        if (keyPreviewView != null) {
54            return keyPreviewView;
55        }
56        keyPreviewView = mFreeKeyPreviewViews.poll();
57        if (keyPreviewView != null) {
58            return keyPreviewView;
59        }
60        final Context context = placerView.getContext();
61        keyPreviewView = new KeyPreviewView(context, null /* attrs */);
62        keyPreviewView.setBackgroundResource(mParams.mPreviewBackgroundResId);
63        placerView.addView(keyPreviewView, ViewLayoutUtils.newLayoutParam(placerView, 0, 0));
64        return keyPreviewView;
65    }
66
67    public boolean isShowingKeyPreview(final Key key) {
68        return mShowingKeyPreviewViews.containsKey(key);
69    }
70
71    public void dismissKeyPreview(final Key key, final boolean withAnimation) {
72        if (key == null) {
73            return;
74        }
75        final KeyPreviewView keyPreviewView = mShowingKeyPreviewViews.get(key);
76        if (keyPreviewView == null) {
77            return;
78        }
79        final Object tag = keyPreviewView.getTag();
80        if (withAnimation) {
81            if (tag instanceof KeyPreviewAnimators) {
82                final KeyPreviewAnimators animators = (KeyPreviewAnimators)tag;
83                animators.startDismiss();
84                return;
85            }
86        }
87        // Dismiss preview without animation.
88        mShowingKeyPreviewViews.remove(key);
89        if (tag instanceof Animator) {
90            ((Animator)tag).cancel();
91        }
92        keyPreviewView.setTag(null);
93        keyPreviewView.setVisibility(View.INVISIBLE);
94        mFreeKeyPreviewViews.add(keyPreviewView);
95    }
96
97    public void placeAndShowKeyPreview(final Key key, final KeyboardIconsSet iconsSet,
98            final KeyDrawParams drawParams, final int keyboardViewWidth, final int[] keyboardOrigin,
99            final ViewGroup placerView, final boolean withAnimation) {
100        final KeyPreviewView keyPreviewView = getKeyPreviewView(key, placerView);
101        placeKeyPreview(
102                key, keyPreviewView, iconsSet, drawParams, keyboardViewWidth, keyboardOrigin);
103        showKeyPreview(key, keyPreviewView, withAnimation);
104    }
105
106    private void placeKeyPreview(final Key key, final KeyPreviewView keyPreviewView,
107            final KeyboardIconsSet iconsSet, final KeyDrawParams drawParams,
108            final int keyboardViewWidth, final int[] originCoords) {
109        keyPreviewView.setPreviewVisual(key, iconsSet, drawParams);
110        keyPreviewView.measure(
111                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
112        mParams.setGeometry(keyPreviewView);
113        final int previewWidth = keyPreviewView.getMeasuredWidth();
114        final int previewHeight = mParams.mPreviewHeight;
115        final int keyDrawWidth = key.getDrawWidth();
116        // The key preview is horizontally aligned with the center of the visible part of the
117        // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and
118        // the left/right background is used if such background is specified.
119        final int keyPreviewPosition;
120        int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2
121                + CoordinateUtils.x(originCoords);
122        if (previewX < 0) {
123            previewX = 0;
124            keyPreviewPosition = KeyPreviewView.POSITION_LEFT;
125        } else if (previewX > keyboardViewWidth - previewWidth) {
126            previewX = keyboardViewWidth - previewWidth;
127            keyPreviewPosition = KeyPreviewView.POSITION_RIGHT;
128        } else {
129            keyPreviewPosition = KeyPreviewView.POSITION_MIDDLE;
130        }
131        final boolean hasMoreKeys = (key.getMoreKeys() != null);
132        keyPreviewView.setPreviewBackground(hasMoreKeys, keyPreviewPosition);
133        // The key preview is placed vertically above the top edge of the parent key with an
134        // arbitrary offset.
135        final int previewY = key.getY() - previewHeight + mParams.mPreviewOffset
136                + CoordinateUtils.y(originCoords);
137
138        ViewLayoutUtils.placeViewAt(
139                keyPreviewView, previewX, previewY, previewWidth, previewHeight);
140        keyPreviewView.setPivotX(previewWidth / 2.0f);
141        keyPreviewView.setPivotY(previewHeight);
142    }
143
144    void showKeyPreview(final Key key, final KeyPreviewView keyPreviewView,
145            final boolean withAnimation) {
146        if (!withAnimation) {
147            keyPreviewView.setVisibility(View.VISIBLE);
148            mShowingKeyPreviewViews.put(key, keyPreviewView);
149            return;
150        }
151
152        // Show preview with animation.
153        final Animator showUpAnimator = createShowUpAnimator(key, keyPreviewView);
154        final Animator dismissAnimator = createDismissAnimator(key, keyPreviewView);
155        final KeyPreviewAnimators animators = new KeyPreviewAnimators(
156                showUpAnimator, dismissAnimator);
157        keyPreviewView.setTag(animators);
158        animators.startShowUp();
159    }
160
161    public Animator createShowUpAnimator(final Key key, final KeyPreviewView keyPreviewView) {
162        final Animator showUpAnimator = mParams.createShowUpAnimator(keyPreviewView);
163        showUpAnimator.addListener(new AnimatorListenerAdapter() {
164            @Override
165            public void onAnimationStart(final Animator animator) {
166                showKeyPreview(key, keyPreviewView, false /* withAnimation */);
167            }
168        });
169        return showUpAnimator;
170    }
171
172    private Animator createDismissAnimator(final Key key, final KeyPreviewView keyPreviewView) {
173        final Animator dismissAnimator = mParams.createDismissAnimator(keyPreviewView);
174        dismissAnimator.addListener(new AnimatorListenerAdapter() {
175            @Override
176            public void onAnimationEnd(final Animator animator) {
177                dismissKeyPreview(key, false /* withAnimation */);
178            }
179        });
180        return dismissAnimator;
181    }
182
183    private static class KeyPreviewAnimators extends AnimatorListenerAdapter {
184        private final Animator mShowUpAnimator;
185        private final Animator mDismissAnimator;
186
187        public KeyPreviewAnimators(final Animator showUpAnimator, final Animator dismissAnimator) {
188            mShowUpAnimator = showUpAnimator;
189            mDismissAnimator = dismissAnimator;
190        }
191
192        public void startShowUp() {
193            mShowUpAnimator.start();
194        }
195
196        public void startDismiss() {
197            if (mShowUpAnimator.isRunning()) {
198                mShowUpAnimator.addListener(this);
199                return;
200            }
201            mDismissAnimator.start();
202        }
203
204        @Override
205        public void onAnimationEnd(final Animator animator) {
206            mDismissAnimator.start();
207        }
208    }
209}
210