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.systemui.recents;
18
19import android.animation.ArgbEvaluator;
20import android.animation.ValueAnimator;
21import android.app.ActivityManager;
22import android.app.ActivityManagerNative;
23import android.content.BroadcastReceiver;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.content.res.Configuration;
28import android.graphics.PixelFormat;
29import android.graphics.drawable.ColorDrawable;
30import android.os.RemoteException;
31import android.util.DisplayMetrics;
32import android.view.Gravity;
33import android.view.View;
34import android.view.ViewGroup;
35import android.view.WindowManager;
36import android.view.accessibility.AccessibilityManager;
37import android.view.animation.DecelerateInterpolator;
38import android.widget.Button;
39import android.widget.FrameLayout;
40import android.widget.LinearLayout;
41import android.widget.TextView;
42
43import com.android.systemui.R;
44
45import java.util.ArrayList;
46
47public class ScreenPinningRequest implements View.OnClickListener {
48    private final Context mContext;
49
50    private final AccessibilityManager mAccessibilityService;
51    private final WindowManager mWindowManager;
52
53    private RequestWindowView mRequestWindow;
54
55    public ScreenPinningRequest(Context context) {
56        mContext = context;
57        mAccessibilityService = (AccessibilityManager)
58                mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
59        mWindowManager = (WindowManager)
60                mContext.getSystemService(Context.WINDOW_SERVICE);
61    }
62
63    public void clearPrompt() {
64        if (mRequestWindow != null) {
65            mWindowManager.removeView(mRequestWindow);
66            mRequestWindow = null;
67        }
68    }
69
70    public void showPrompt(boolean allowCancel) {
71        clearPrompt();
72
73        mRequestWindow = new RequestWindowView(mContext, allowCancel);
74
75        mRequestWindow.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
76
77        // show the confirmation
78        WindowManager.LayoutParams lp = getWindowLayoutParams();
79        mWindowManager.addView(mRequestWindow, lp);
80    }
81
82    public void onConfigurationChanged() {
83        if (mRequestWindow != null) {
84            mRequestWindow.onConfigurationChanged();
85        }
86    }
87
88    private WindowManager.LayoutParams getWindowLayoutParams() {
89        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
90                ViewGroup.LayoutParams.MATCH_PARENT,
91                ViewGroup.LayoutParams.MATCH_PARENT,
92                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
93                0
94                        | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
95                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
96                        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
97                ,
98                PixelFormat.TRANSLUCENT);
99        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
100        lp.setTitle("ScreenPinningConfirmation");
101        lp.gravity = Gravity.FILL;
102        return lp;
103    }
104
105    @Override
106    public void onClick(View v) {
107        if (v.getId() == R.id.screen_pinning_ok_button || mRequestWindow == v) {
108            try {
109                ActivityManagerNative.getDefault().startLockTaskModeOnCurrent();
110            } catch (RemoteException e) {}
111        }
112        clearPrompt();
113    }
114
115    public FrameLayout.LayoutParams getRequestLayoutParams(boolean isLandscape) {
116        return new FrameLayout.LayoutParams(
117                ViewGroup.LayoutParams.WRAP_CONTENT,
118                ViewGroup.LayoutParams.WRAP_CONTENT,
119                isLandscape ? (Gravity.CENTER_VERTICAL | Gravity.RIGHT)
120                            : (Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM));
121    }
122
123    private class RequestWindowView extends FrameLayout {
124        private static final int OFFSET_DP = 96;
125
126        private final ColorDrawable mColor = new ColorDrawable(0);
127        private ValueAnimator mColorAnim;
128        private ViewGroup mLayout;
129        private boolean mShowCancel;
130
131        public RequestWindowView(Context context, boolean showCancel) {
132            super(context);
133            setClickable(true);
134            setOnClickListener(ScreenPinningRequest.this);
135            setBackground(mColor);
136            mShowCancel = showCancel;
137        }
138
139        @Override
140        public void onAttachedToWindow() {
141            DisplayMetrics metrics = new DisplayMetrics();
142            mWindowManager.getDefaultDisplay().getMetrics(metrics);
143            float density = metrics.density;
144            boolean isLandscape = isLandscapePhone(mContext);
145
146            inflateView(isLandscape);
147            int bgColor = mContext.getColor(
148                    R.color.screen_pinning_request_window_bg);
149            if (ActivityManager.isHighEndGfx()) {
150                mLayout.setAlpha(0f);
151                if (isLandscape) {
152                    mLayout.setTranslationX(OFFSET_DP * density);
153                } else {
154                    mLayout.setTranslationY(OFFSET_DP * density);
155                }
156                mLayout.animate()
157                        .alpha(1f)
158                        .translationX(0)
159                        .translationY(0)
160                        .setDuration(300)
161                        .setInterpolator(new DecelerateInterpolator())
162                        .start();
163
164                mColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(), 0, bgColor);
165                mColorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
166                    @Override
167                    public void onAnimationUpdate(ValueAnimator animation) {
168                        final int c = (Integer) animation.getAnimatedValue();
169                        mColor.setColor(c);
170                    }
171                });
172                mColorAnim.setDuration(1000);
173                mColorAnim.start();
174            } else {
175                mColor.setColor(bgColor);
176            }
177
178            IntentFilter filter = new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED);
179            filter.addAction(Intent.ACTION_USER_SWITCHED);
180            filter.addAction(Intent.ACTION_SCREEN_OFF);
181            mContext.registerReceiver(mReceiver, filter);
182        }
183
184        private boolean isLandscapePhone(Context context) {
185            Configuration config = mContext.getResources().getConfiguration();
186            return config.orientation == Configuration.ORIENTATION_LANDSCAPE
187                    && config.smallestScreenWidthDp < 600;
188        }
189
190        private void inflateView(boolean isLandscape) {
191            // We only want this landscape orientation on <600dp, so rather than handle
192            // resource overlay for -land and -sw600dp-land, just inflate this
193            // other view for this single case.
194            mLayout = (ViewGroup) View.inflate(getContext(), isLandscape
195                    ? R.layout.screen_pinning_request_land_phone : R.layout.screen_pinning_request,
196                    null);
197            // Catch touches so they don't trigger cancel/activate, like outside does.
198            mLayout.setClickable(true);
199            // Status bar is always on the right.
200            mLayout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
201            // Buttons and text do switch sides though.
202            View buttons = mLayout.findViewById(R.id.screen_pinning_buttons);
203            buttons.setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE);
204            mLayout.findViewById(R.id.screen_pinning_text_area)
205                    .setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE);
206            swapChildrenIfRtlAndVertical(buttons);
207
208            ((Button) mLayout.findViewById(R.id.screen_pinning_ok_button))
209                    .setOnClickListener(ScreenPinningRequest.this);
210            if (mShowCancel) {
211                ((Button) mLayout.findViewById(R.id.screen_pinning_cancel_button))
212                        .setOnClickListener(ScreenPinningRequest.this);
213            } else {
214                ((Button) mLayout.findViewById(R.id.screen_pinning_cancel_button))
215                        .setVisibility(View.INVISIBLE);
216            }
217
218            final int description = mAccessibilityService.isEnabled()
219                    ? R.string.screen_pinning_description_accessible
220                    : R.string.screen_pinning_description;
221            ((TextView) mLayout.findViewById(R.id.screen_pinning_description))
222                    .setText(description);
223            final int backBgVisibility =
224                    mAccessibilityService.isEnabled() ? View.INVISIBLE : View.VISIBLE;
225            mLayout.findViewById(R.id.screen_pinning_back_bg).setVisibility(backBgVisibility);
226            mLayout.findViewById(R.id.screen_pinning_back_bg_light).setVisibility(backBgVisibility);
227
228            addView(mLayout, getRequestLayoutParams(isLandscape));
229        }
230
231        private void swapChildrenIfRtlAndVertical(View group) {
232            if (mContext.getResources().getConfiguration().getLayoutDirection()
233                    != View.LAYOUT_DIRECTION_RTL) {
234                return;
235            }
236            LinearLayout linearLayout = (LinearLayout) group;
237            if (linearLayout.getOrientation() == LinearLayout.VERTICAL) {
238                int childCount = linearLayout.getChildCount();
239                ArrayList<View> childList = new ArrayList<>(childCount);
240                for (int i = 0; i < childCount; i++) {
241                    childList.add(linearLayout.getChildAt(i));
242                }
243                linearLayout.removeAllViews();
244                for (int i = childCount - 1; i >= 0; i--) {
245                    linearLayout.addView(childList.get(i));
246                }
247            }
248        }
249
250        @Override
251        public void onDetachedFromWindow() {
252            mContext.unregisterReceiver(mReceiver);
253        }
254
255        protected void onConfigurationChanged() {
256            removeAllViews();
257            inflateView(isLandscapePhone(mContext));
258        }
259
260        private final Runnable mUpdateLayoutRunnable = new Runnable() {
261            @Override
262            public void run() {
263                if (mLayout != null && mLayout.getParent() != null) {
264                    mLayout.setLayoutParams(getRequestLayoutParams(isLandscapePhone(mContext)));
265                }
266            }
267        };
268
269        private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
270            @Override
271            public void onReceive(Context context, Intent intent) {
272                if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
273                    post(mUpdateLayoutRunnable);
274                } else if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED)
275                        || intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
276                    clearPrompt();
277                }
278            }
279        };
280    }
281
282}
283