blob: 64dab180dd45eb0a3a28d9ef36ec8e7f0ac7ae17 [file] [log] [blame]
nxpandroid64fd68c2015-09-23 16:45:15 +05301/*
2 * Copyright (C) 2011 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 */
nxf500513a018e72019-04-23 17:11:41 +053016
nxpandroid64fd68c2015-09-23 16:45:15 +053017package com.android.nfc.beam;
18
nxpandroid64fd68c2015-09-23 16:45:15 +053019import com.android.nfc.R;
20import com.android.nfc.beam.FireflyRenderer;
21
22import android.animation.Animator;
23import android.animation.AnimatorSet;
24import android.animation.ObjectAnimator;
25import android.animation.PropertyValuesHolder;
26import android.animation.TimeAnimator;
27import android.app.ActivityManager;
28import android.app.StatusBarManager;
nxpandroidb7819802017-09-14 11:55:40 +053029import android.content.BroadcastReceiver;
nxpandroid64fd68c2015-09-23 16:45:15 +053030import android.content.Context;
nxpandroidb7819802017-09-14 11:55:40 +053031import android.content.Intent;
32import android.content.IntentFilter;
nxpandroid64fd68c2015-09-23 16:45:15 +053033import android.content.pm.ActivityInfo;
34import android.content.res.Configuration;
35import android.graphics.Bitmap;
36import android.graphics.Canvas;
37import android.graphics.Matrix;
38import android.graphics.PixelFormat;
Ganesh Deva0e6fd382018-08-07 13:13:23 +053039import android.graphics.Rect;
nxpandroid64fd68c2015-09-23 16:45:15 +053040import android.graphics.SurfaceTexture;
41import android.os.AsyncTask;
42import android.os.Binder;
43import android.util.DisplayMetrics;
44import android.util.Log;
45import android.view.ActionMode;
46import android.view.Display;
47import android.view.KeyEvent;
nxpandroid281eb922016-08-25 20:27:46 +053048import android.view.KeyboardShortcutGroup;
nxpandroid64fd68c2015-09-23 16:45:15 +053049import android.view.LayoutInflater;
50import android.view.Menu;
51import android.view.MenuItem;
52import android.view.MotionEvent;
nxpandroid1153eb32015-11-06 18:46:58 +053053import com.android.internal.policy.PhoneWindow;
54import android.view.SearchEvent;
nxpandroid64fd68c2015-09-23 16:45:15 +053055import android.view.Surface;
56import android.view.SurfaceControl;
57import android.view.TextureView;
58import android.view.View;
59import android.view.ViewGroup;
60import android.view.Window;
61import android.view.WindowManager;
62import android.view.WindowManager.LayoutParams;
63import android.view.accessibility.AccessibilityEvent;
64import android.view.animation.AccelerateDecelerateInterpolator;
65import android.view.animation.DecelerateInterpolator;
66import android.widget.ImageView;
67import android.widget.TextView;
68import android.widget.Toast;
69
nxpandroid281eb922016-08-25 20:27:46 +053070import java.util.List;
71
nxpandroid64fd68c2015-09-23 16:45:15 +053072/**
73 * This class is responsible for handling the UI animation
74 * around Android Beam. The animation consists of the following
75 * animators:
76 *
77 * mPreAnimator: scales the screenshot down to INTERMEDIATE_SCALE
78 * mSlowSendAnimator: scales the screenshot down to 0.2f (used as a "send in progress" animation)
79 * mFastSendAnimator: quickly scales the screenshot down to 0.0f (used for send success)
80 * mFadeInAnimator: fades the current activity back in (used after mFastSendAnimator completes)
81 * mScaleUpAnimator: scales the screenshot back up to full screen (used for failure or receiving)
82 * mHintAnimator: Slowly turns up the alpha of the "Touch to Beam" hint
83 *
84 * Possible sequences are:
85 *
86 * mPreAnimator => mSlowSendAnimator => mFastSendAnimator => mFadeInAnimator (send success)
87 * mPreAnimator => mSlowSendAnimator => mScaleUpAnimator (send failure)
88 * mPreAnimator => mScaleUpAnimator (p2p link broken, or data received)
89 *
90 * Note that mFastSendAnimator and mFadeInAnimator are combined in a set, as they
91 * are an atomic animation that cannot be interrupted.
92 *
93 * All methods of this class must be called on the UI thread
94 */
95public class SendUi implements Animator.AnimatorListener, View.OnTouchListener,
96 TimeAnimator.TimeListener, TextureView.SurfaceTextureListener, android.view.Window.Callback {
97 static final String TAG = "SendUi";
98
99 static final float INTERMEDIATE_SCALE = 0.6f;
100
101 static final float[] PRE_SCREENSHOT_SCALE = {1.0f, INTERMEDIATE_SCALE};
102 static final int PRE_DURATION_MS = 350;
103
104 static final float[] SEND_SCREENSHOT_SCALE = {INTERMEDIATE_SCALE, 0.2f};
105 static final int SLOW_SEND_DURATION_MS = 8000; // Stretch out sending over 8s
106 static final int FAST_SEND_DURATION_MS = 350;
107
108 static final float[] SCALE_UP_SCREENSHOT_SCALE = {INTERMEDIATE_SCALE, 1.0f};
109 static final int SCALE_UP_DURATION_MS = 300;
110
111 static final int FADE_IN_DURATION_MS = 250;
112 static final int FADE_IN_START_DELAY_MS = 350;
113
114 static final int SLIDE_OUT_DURATION_MS = 300;
115
116 static final float[] BLACK_LAYER_ALPHA_DOWN_RANGE = {0.9f, 0.0f};
117 static final float[] BLACK_LAYER_ALPHA_UP_RANGE = {0.0f, 0.9f};
118
119 static final float[] TEXT_HINT_ALPHA_RANGE = {0.0f, 1.0f};
120 static final int TEXT_HINT_ALPHA_DURATION_MS = 500;
121 static final int TEXT_HINT_ALPHA_START_DELAY_MS = 300;
122
123 public static final int FINISH_SCALE_UP = 0;
124 public static final int FINISH_SEND_SUCCESS = 1;
125
126 static final int STATE_IDLE = 0;
127 static final int STATE_W4_SCREENSHOT = 1;
128 static final int STATE_W4_SCREENSHOT_PRESEND_REQUESTED = 2;
129 static final int STATE_W4_SCREENSHOT_PRESEND_NFC_TAP_REQUESTED = 3;
130 static final int STATE_W4_SCREENSHOT_THEN_STOP = 4;
131 static final int STATE_W4_PRESEND = 5;
132 static final int STATE_W4_TOUCH = 6;
133 static final int STATE_W4_NFC_TAP = 7;
134 static final int STATE_SENDING = 8;
135 static final int STATE_COMPLETE = 9;
136
137 // all members are only used on UI thread
138 final WindowManager mWindowManager;
139 final Context mContext;
140 final Display mDisplay;
141 final DisplayMetrics mDisplayMetrics;
142 final Matrix mDisplayMatrix;
143 final WindowManager.LayoutParams mWindowLayoutParams;
144 final LayoutInflater mLayoutInflater;
145 final StatusBarManager mStatusBarManager;
146 final View mScreenshotLayout;
147 final ImageView mScreenshotView;
148 final ImageView mBlackLayer;
149 final TextureView mTextureView;
150 final TextView mTextHint;
151 final TextView mTextRetry;
152 final Callback mCallback;
153
154 // The mFrameCounter animation is purely used to count down a certain
155 // number of (vsync'd) frames. This is needed because the first 3
156 // times the animation internally calls eglSwapBuffers(), large buffers
157 // are allocated by the graphics drivers. This causes the animation
158 // to look janky. So on platforms where we can use hardware acceleration,
159 // the animation order is:
160 // Wait for hw surface => start frame counter => start pre-animation after 3 frames
161 // For platforms where no hw acceleration can be used, the pre-animation
162 // is started immediately.
163 final TimeAnimator mFrameCounterAnimator;
164
165 final ObjectAnimator mPreAnimator;
166 final ObjectAnimator mSlowSendAnimator;
167 final ObjectAnimator mFastSendAnimator;
168 final ObjectAnimator mFadeInAnimator;
169 final ObjectAnimator mHintAnimator;
170 final ObjectAnimator mScaleUpAnimator;
171 final ObjectAnimator mAlphaDownAnimator;
172 final ObjectAnimator mAlphaUpAnimator;
173 final AnimatorSet mSuccessAnimatorSet;
174
nxpandroid281eb922016-08-25 20:27:46 +0530175 // Besides animating the screenshot, the Beam UI also renders
nxpandroid64fd68c2015-09-23 16:45:15 +0530176 // fireflies on platforms where we can do hardware-acceleration.
177 // Firefly rendering is only started once the initial
178 // "pre-animation" has scaled down the screenshot, to avoid
179 // that animation becoming janky. Likewise, the fireflies are
180 // stopped in their tracks as soon as we finish the animation,
181 // to make the finishing animation smooth.
182 final boolean mHardwareAccelerated;
183 final FireflyRenderer mFireflyRenderer;
184
185 String mToastString;
186 Bitmap mScreenshotBitmap;
187
188 int mState;
189 int mRenderedFrames;
190
191 View mDecor;
192
193 // Used for holding the surface
194 SurfaceTexture mSurface;
195 int mSurfaceWidth;
196 int mSurfaceHeight;
197
198 public interface Callback {
199 public void onSendConfirmed();
200 public void onCanceled();
201 }
202
203 public SendUi(Context context, Callback callback) {
204 mContext = context;
205 mCallback = callback;
206
207 mDisplayMetrics = new DisplayMetrics();
208 mDisplayMatrix = new Matrix();
209 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
210 mStatusBarManager = (StatusBarManager) context.getSystemService(Context.STATUS_BAR_SERVICE);
211
212 mDisplay = mWindowManager.getDefaultDisplay();
213
214 mLayoutInflater = (LayoutInflater)
215 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
216 mScreenshotLayout = mLayoutInflater.inflate(R.layout.screenshot, null);
217
218 mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.screenshot);
219 mScreenshotLayout.setFocusable(true);
220
221 mTextHint = (TextView) mScreenshotLayout.findViewById(R.id.calltoaction);
222 mTextRetry = (TextView) mScreenshotLayout.findViewById(R.id.retrytext);
223 mBlackLayer = (ImageView) mScreenshotLayout.findViewById(R.id.blacklayer);
224 mTextureView = (TextureView) mScreenshotLayout.findViewById(R.id.fireflies);
225 mTextureView.setSurfaceTextureListener(this);
226
227 // We're only allowed to use hardware acceleration if
228 // isHighEndGfx() returns true - otherwise, we're too limited
229 // on resources to do it.
230 mHardwareAccelerated = ActivityManager.isHighEndGfx();
231 int hwAccelerationFlags = mHardwareAccelerated ?
232 WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED : 0;
233
234 mWindowLayoutParams = new WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
235 ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
236 WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
237 WindowManager.LayoutParams.FLAG_FULLSCREEN
238 | hwAccelerationFlags
239 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
240 PixelFormat.OPAQUE);
241 mWindowLayoutParams.privateFlags |=
242 WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
243 mWindowLayoutParams.token = new Binder();
244
245 mFrameCounterAnimator = new TimeAnimator();
246 mFrameCounterAnimator.setTimeListener(this);
247
248 PropertyValuesHolder preX = PropertyValuesHolder.ofFloat("scaleX", PRE_SCREENSHOT_SCALE);
249 PropertyValuesHolder preY = PropertyValuesHolder.ofFloat("scaleY", PRE_SCREENSHOT_SCALE);
250 mPreAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, preX, preY);
251 mPreAnimator.setInterpolator(new DecelerateInterpolator());
252 mPreAnimator.setDuration(PRE_DURATION_MS);
253 mPreAnimator.addListener(this);
254
255 PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX", SEND_SCREENSHOT_SCALE);
256 PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY", SEND_SCREENSHOT_SCALE);
257 PropertyValuesHolder alphaDown = PropertyValuesHolder.ofFloat("alpha",
258 new float[]{1.0f, 0.0f});
259
260 mSlowSendAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, postX, postY);
261 mSlowSendAnimator.setInterpolator(new DecelerateInterpolator());
262 mSlowSendAnimator.setDuration(SLOW_SEND_DURATION_MS);
263
264 mFastSendAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, postX,
265 postY, alphaDown);
266 mFastSendAnimator.setInterpolator(new DecelerateInterpolator());
267 mFastSendAnimator.setDuration(FAST_SEND_DURATION_MS);
268 mFastSendAnimator.addListener(this);
269
270 PropertyValuesHolder scaleUpX = PropertyValuesHolder.ofFloat("scaleX", SCALE_UP_SCREENSHOT_SCALE);
271 PropertyValuesHolder scaleUpY = PropertyValuesHolder.ofFloat("scaleY", SCALE_UP_SCREENSHOT_SCALE);
272
273 mScaleUpAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, scaleUpX, scaleUpY);
274 mScaleUpAnimator.setInterpolator(new DecelerateInterpolator());
275 mScaleUpAnimator.setDuration(SCALE_UP_DURATION_MS);
276 mScaleUpAnimator.addListener(this);
277
278 PropertyValuesHolder fadeIn = PropertyValuesHolder.ofFloat("alpha", 1.0f);
279 mFadeInAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, fadeIn);
280 mFadeInAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
281 mFadeInAnimator.setDuration(FADE_IN_DURATION_MS);
282 mFadeInAnimator.setStartDelay(FADE_IN_START_DELAY_MS);
283 mFadeInAnimator.addListener(this);
284
285 PropertyValuesHolder alphaUp = PropertyValuesHolder.ofFloat("alpha", TEXT_HINT_ALPHA_RANGE);
286 mHintAnimator = ObjectAnimator.ofPropertyValuesHolder(mTextHint, alphaUp);
287 mHintAnimator.setInterpolator(null);
288 mHintAnimator.setDuration(TEXT_HINT_ALPHA_DURATION_MS);
289 mHintAnimator.setStartDelay(TEXT_HINT_ALPHA_START_DELAY_MS);
290
291 alphaDown = PropertyValuesHolder.ofFloat("alpha", BLACK_LAYER_ALPHA_DOWN_RANGE);
292 mAlphaDownAnimator = ObjectAnimator.ofPropertyValuesHolder(mBlackLayer, alphaDown);
293 mAlphaDownAnimator.setInterpolator(new DecelerateInterpolator());
294 mAlphaDownAnimator.setDuration(400);
295
296 alphaUp = PropertyValuesHolder.ofFloat("alpha", BLACK_LAYER_ALPHA_UP_RANGE);
297 mAlphaUpAnimator = ObjectAnimator.ofPropertyValuesHolder(mBlackLayer, alphaUp);
298 mAlphaUpAnimator.setInterpolator(new DecelerateInterpolator());
299 mAlphaUpAnimator.setDuration(200);
300
301 mSuccessAnimatorSet = new AnimatorSet();
302 mSuccessAnimatorSet.playSequentially(mFastSendAnimator, mFadeInAnimator);
303
304 // Create a Window with a Decor view; creating a window allows us to get callbacks
305 // on key events (which require a decor view to be dispatched).
306 mContext.setTheme(android.R.style.Theme_Black_NoTitleBar_Fullscreen);
nxpandroid1153eb32015-11-06 18:46:58 +0530307 Window window = new PhoneWindow(mContext);
nxpandroid64fd68c2015-09-23 16:45:15 +0530308 window.setCallback(this);
309 window.requestFeature(Window.FEATURE_NO_TITLE);
310 mDecor = window.getDecorView();
311 window.setContentView(mScreenshotLayout, mWindowLayoutParams);
312
313 if (mHardwareAccelerated) {
314 mFireflyRenderer = new FireflyRenderer(context);
315 } else {
316 mFireflyRenderer = null;
317 }
318 mState = STATE_IDLE;
319 }
320
321 public void takeScreenshot() {
322 // There's no point in taking the screenshot if
323 // we're still finishing the previous animation.
324 if (mState >= STATE_W4_TOUCH) {
325 return;
326 }
327 mState = STATE_W4_SCREENSHOT;
328 new ScreenshotTask().execute();
nxpandroidb7819802017-09-14 11:55:40 +0530329
330 final IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
331 mContext.registerReceiver(mReceiver, filter);
nxpandroid64fd68c2015-09-23 16:45:15 +0530332 }
333
334 /** Show pre-send animation */
335 public void showPreSend(boolean promptToNfcTap) {
336 switch (mState) {
337 case STATE_IDLE:
338 Log.e(TAG, "Unexpected showPreSend() in STATE_IDLE");
339 return;
340 case STATE_W4_SCREENSHOT:
341 // Still waiting for screenshot, store request in state
342 // and wait for screenshot completion.
343 if (promptToNfcTap) {
344 mState = STATE_W4_SCREENSHOT_PRESEND_NFC_TAP_REQUESTED;
345 } else {
346 mState = STATE_W4_SCREENSHOT_PRESEND_REQUESTED;
347 }
348 return;
349 case STATE_W4_SCREENSHOT_PRESEND_REQUESTED:
350 case STATE_W4_SCREENSHOT_PRESEND_NFC_TAP_REQUESTED:
351 Log.e(TAG, "Unexpected showPreSend() in STATE_W4_SCREENSHOT_PRESEND_REQUESTED");
352 return;
353 case STATE_W4_PRESEND:
354 // Expected path, continue below
355 break;
356 default:
357 Log.e(TAG, "Unexpected showPreSend() in state " + Integer.toString(mState));
358 return;
359 }
360 // Update display metrics
361 mDisplay.getRealMetrics(mDisplayMetrics);
362
363 final int statusBarHeight = mContext.getResources().getDimensionPixelSize(
364 com.android.internal.R.dimen.status_bar_height);
365
366 mBlackLayer.setVisibility(View.GONE);
367 mBlackLayer.setAlpha(0f);
368 mScreenshotLayout.setOnTouchListener(this);
369 mScreenshotView.setImageBitmap(mScreenshotBitmap);
370 mScreenshotView.setTranslationX(0f);
371 mScreenshotView.setAlpha(1.0f);
372 mScreenshotView.setPadding(0, statusBarHeight, 0, 0);
373
374 mScreenshotLayout.requestFocus();
375
376 if (promptToNfcTap) {
377 mTextHint.setText(mContext.getResources().getString(R.string.ask_nfc_tap));
378 } else {
nxpandroid281eb922016-08-25 20:27:46 +0530379 mTextHint.setText(mContext.getResources().getString(R.string.tap_to_beam));
nxpandroid64fd68c2015-09-23 16:45:15 +0530380 }
381 mTextHint.setAlpha(0.0f);
382 mTextHint.setVisibility(View.VISIBLE);
383 mHintAnimator.start();
384
385 // Lock the orientation.
386 // The orientation from the configuration does not specify whether
387 // the orientation is reverse or not (ie landscape or reverse landscape).
388 // So we have to use SENSOR_LANDSCAPE or SENSOR_PORTRAIT to make sure
389 // we lock in portrait / landscape and have the sensor determine
390 // which way is up.
391 int orientation = mContext.getResources().getConfiguration().orientation;
392
393 switch (orientation) {
394 case Configuration.ORIENTATION_LANDSCAPE:
395 mWindowLayoutParams.screenOrientation =
396 ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
397 break;
398 case Configuration.ORIENTATION_PORTRAIT:
399 mWindowLayoutParams.screenOrientation =
400 ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
401 break;
402 default:
403 mWindowLayoutParams.screenOrientation =
404 ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
405 break;
406 }
407
408 mWindowManager.addView(mDecor, mWindowLayoutParams);
409 // Disable statusbar pull-down
410 mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND);
411
412 mToastString = null;
413
414 if (!mHardwareAccelerated) {
415 mPreAnimator.start();
416 } // else, we will start the animation once we get the hardware surface
417 mState = promptToNfcTap ? STATE_W4_NFC_TAP : STATE_W4_TOUCH;
418 }
419
420 /** Show starting send animation */
421 public void showStartSend() {
422 if (mState < STATE_SENDING) return;
423
424 mTextRetry.setVisibility(View.GONE);
425 // Update the starting scale - touchscreen-mashers may trigger
426 // this before the pre-animation completes.
427 float currentScale = mScreenshotView.getScaleX();
428 PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX",
429 new float[] {currentScale, 0.0f});
430 PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY",
431 new float[] {currentScale, 0.0f});
432
433 mSlowSendAnimator.setValues(postX, postY);
434
435 float currentAlpha = mBlackLayer.getAlpha();
436 if (mBlackLayer.isShown() && currentAlpha > 0.0f) {
nxpandroid281eb922016-08-25 20:27:46 +0530437 PropertyValuesHolder alphaDown = PropertyValuesHolder.ofFloat("alpha",
nxpandroid64fd68c2015-09-23 16:45:15 +0530438 new float[] {currentAlpha, 0.0f});
439 mAlphaDownAnimator.setValues(alphaDown);
440 mAlphaDownAnimator.start();
441 }
442 mSlowSendAnimator.start();
443 }
444
445 public void finishAndToast(int finishMode, String toast) {
446 mToastString = toast;
447
448 finish(finishMode);
449 }
450
451 /** Return to initial state */
452 public void finish(int finishMode) {
453 switch (mState) {
454 case STATE_IDLE:
455 return;
456 case STATE_W4_SCREENSHOT:
457 case STATE_W4_SCREENSHOT_PRESEND_REQUESTED:
458 case STATE_W4_SCREENSHOT_PRESEND_NFC_TAP_REQUESTED:
459 // Screenshot is still being captured on a separate thread.
460 // Update state, and stop everything when the capture is done.
461 mState = STATE_W4_SCREENSHOT_THEN_STOP;
462 return;
463 case STATE_W4_SCREENSHOT_THEN_STOP:
464 Log.e(TAG, "Unexpected call to finish() in STATE_W4_SCREENSHOT_THEN_STOP");
465 return;
466 case STATE_W4_PRESEND:
467 // We didn't build up any animation state yet, but
468 // did store the bitmap. Clear out the bitmap, reset
469 // state and bail.
nxf500513a018e72019-04-23 17:11:41 +0530470 mScreenshotBitmap = null;
nxpandroid64fd68c2015-09-23 16:45:15 +0530471 mState = STATE_IDLE;
472 return;
473 default:
474 // We've started animations and attached a view; tear stuff down below.
475 break;
476 }
477
478 // Stop rendering the fireflies
479 if (mFireflyRenderer != null) {
480 mFireflyRenderer.stop();
481 }
482
483 mTextHint.setVisibility(View.GONE);
484 mTextRetry.setVisibility(View.GONE);
485
486 float currentScale = mScreenshotView.getScaleX();
487 float currentAlpha = mScreenshotView.getAlpha();
488
489 if (finishMode == FINISH_SCALE_UP) {
490 mBlackLayer.setVisibility(View.GONE);
491 PropertyValuesHolder scaleUpX = PropertyValuesHolder.ofFloat("scaleX",
492 new float[] {currentScale, 1.0f});
493 PropertyValuesHolder scaleUpY = PropertyValuesHolder.ofFloat("scaleY",
494 new float[] {currentScale, 1.0f});
495 PropertyValuesHolder scaleUpAlpha = PropertyValuesHolder.ofFloat("alpha",
496 new float[] {currentAlpha, 1.0f});
497 mScaleUpAnimator.setValues(scaleUpX, scaleUpY, scaleUpAlpha);
498
499 mScaleUpAnimator.start();
500 } else if (finishMode == FINISH_SEND_SUCCESS){
501 // Modify the fast send parameters to match the current scale
502 PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX",
503 new float[] {currentScale, 0.0f});
504 PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY",
505 new float[] {currentScale, 0.0f});
506 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha",
507 new float[] {currentAlpha, 0.0f});
508 mFastSendAnimator.setValues(postX, postY, alpha);
509
510 // Reset the fadeIn parameters to start from alpha 1
511 PropertyValuesHolder fadeIn = PropertyValuesHolder.ofFloat("alpha",
512 new float[] {0.0f, 1.0f});
513 mFadeInAnimator.setValues(fadeIn);
514
515 mSlowSendAnimator.cancel();
516 mSuccessAnimatorSet.start();
517 }
518 mState = STATE_COMPLETE;
519 }
520
521 void dismiss() {
522 if (mState < STATE_W4_TOUCH) return;
523 // Immediately set to IDLE, to prevent .cancel() calls
524 // below from immediately calling into dismiss() again.
525 // (They can do so on the same thread).
526 mState = STATE_IDLE;
527 mSurface = null;
528 mFrameCounterAnimator.cancel();
529 mPreAnimator.cancel();
530 mSlowSendAnimator.cancel();
531 mFastSendAnimator.cancel();
532 mSuccessAnimatorSet.cancel();
533 mScaleUpAnimator.cancel();
534 mAlphaUpAnimator.cancel();
535 mAlphaDownAnimator.cancel();
536 mWindowManager.removeView(mDecor);
537 mStatusBarManager.disable(StatusBarManager.DISABLE_NONE);
nxf500513a018e72019-04-23 17:11:41 +0530538 mScreenshotBitmap = null;
nxpandroidb7819802017-09-14 11:55:40 +0530539 mContext.unregisterReceiver(mReceiver);
nxpandroid64fd68c2015-09-23 16:45:15 +0530540 if (mToastString != null) {
nxpandroid6fd9cdb2017-07-12 18:25:41 +0530541 Toast toast = Toast.makeText(mContext, mToastString, Toast.LENGTH_LONG);
542 toast.getWindowParams().privateFlags |= LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
543 toast.show();
nxpandroid64fd68c2015-09-23 16:45:15 +0530544 }
545 mToastString = null;
546 }
547
548 /**
549 * @return the current display rotation in degrees
550 */
551 static float getDegreesForRotation(int value) {
552 switch (value) {
553 case Surface.ROTATION_90:
554 return 90f;
555 case Surface.ROTATION_180:
556 return 180f;
557 case Surface.ROTATION_270:
558 return 270f;
559 }
560 return 0f;
561 }
562
563 final class ScreenshotTask extends AsyncTask<Void, Void, Bitmap> {
564 @Override
565 protected Bitmap doInBackground(Void... params) {
566 return createScreenshot();
567 }
568
569 @Override
570 protected void onPostExecute(Bitmap result) {
571 if (mState == STATE_W4_SCREENSHOT) {
572 // Screenshot done, wait for request to start preSend anim
nxpandroid1153eb32015-11-06 18:46:58 +0530573 mScreenshotBitmap = result;
nxpandroid64fd68c2015-09-23 16:45:15 +0530574 mState = STATE_W4_PRESEND;
575 } else if (mState == STATE_W4_SCREENSHOT_THEN_STOP) {
576 // We were asked to finish, move to idle state and exit
577 mState = STATE_IDLE;
578 } else if (mState == STATE_W4_SCREENSHOT_PRESEND_REQUESTED ||
579 mState == STATE_W4_SCREENSHOT_PRESEND_NFC_TAP_REQUESTED) {
580 if (result != null) {
581 mScreenshotBitmap = result;
582 boolean requestTap = (mState == STATE_W4_SCREENSHOT_PRESEND_NFC_TAP_REQUESTED);
583 mState = STATE_W4_PRESEND;
584 showPreSend(requestTap);
585 } else {
586 // Failed to take screenshot; reset state to idle
587 // and don't do anything
588 Log.e(TAG, "Failed to create screenshot");
589 mState = STATE_IDLE;
590 }
591 } else {
592 Log.e(TAG, "Invalid state on screenshot completion: " + Integer.toString(mState));
593 }
594 }
595 };
596
nxpandroid281eb922016-08-25 20:27:46 +0530597 /**
nxpandroid64fd68c2015-09-23 16:45:15 +0530598 * Returns a screenshot of the current display contents.
599 */
600 Bitmap createScreenshot() {
nxpandroid64fd68c2015-09-23 16:45:15 +0530601 boolean hasNavBar = mContext.getResources().getBoolean(
602 com.android.internal.R.bool.config_showNavigationBar);
nxpandroid64fd68c2015-09-23 16:45:15 +0530603 final int statusBarHeight = mContext.getResources().getDimensionPixelSize(
604 com.android.internal.R.dimen.status_bar_height);
605
606 // Navbar has different sizes, depending on orientation
607 final int navBarHeight = hasNavBar ? mContext.getResources().getDimensionPixelSize(
608 com.android.internal.R.dimen.navigation_bar_height) : 0;
609 final int navBarHeightLandscape = hasNavBar ? mContext.getResources().getDimensionPixelSize(
610 com.android.internal.R.dimen.navigation_bar_height_landscape) : 0;
611
612 final int navBarWidth = hasNavBar ? mContext.getResources().getDimensionPixelSize(
613 com.android.internal.R.dimen.navigation_bar_width) : 0;
614
Ganesh Deva0e6fd382018-08-07 13:13:23 +0530615 mDisplay.getRealMetrics(mDisplayMetrics);
616 float smallestWidth = (float)Math.min(mDisplayMetrics.widthPixels,
617 mDisplayMetrics.heightPixels);
618 float smallestWidthDp = smallestWidth / (mDisplayMetrics.densityDpi / 160f);
619
620 int rot = mDisplay.getRotation();
621
622 // TODO this is somewhat device-specific; need generic solution.
623 // The starting crop for the screenshot is the fullscreen without the status bar, which
624 // is always on top. The conditional check will determine how to crop the navbar,
625 // depending on orienation and screen size.
626 Rect crop = new Rect(0, statusBarHeight, mDisplayMetrics.widthPixels,
627 mDisplayMetrics.heightPixels);
628 if (mDisplayMetrics.widthPixels < mDisplayMetrics.heightPixels) {
629 // Portrait mode: crop the navbar out from the bottom, width unchanged
630 crop.bottom -= navBarHeight;
631 } else {
632 // Landscape mode:
633 if (smallestWidthDp > 599) {
634 // Navbar on bottom on >599dp width devices, so crop navbar out from the bottom.
635 crop.bottom -= navBarHeightLandscape;
636 } else {
637 // Navbar on right, so crop navbar out from right of screen.
638 crop.right -= navBarWidth;
639 }
nxpandroid64fd68c2015-09-23 16:45:15 +0530640 }
641
Ganesh Deva0e6fd382018-08-07 13:13:23 +0530642 int width = crop.width();
643 int height = crop.height();
644 // Take the screenshot. SurfaceControl will generate a hardware bitmap in the correct
645 // orientation and size.
646 Bitmap bitmap = SurfaceControl.screenshot(crop, width, height, rot);
nxpandroid64fd68c2015-09-23 16:45:15 +0530647 // Bail if we couldn't take the screenshot
648 if (bitmap == null) {
649 return null;
650 }
nxpandroid281eb922016-08-25 20:27:46 +0530651
Ganesh Deva0e6fd382018-08-07 13:13:23 +0530652 // Convert to a software bitmap so it can be set in an ImageView.
653 Bitmap swBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
654 return swBitmap;
nxpandroid64fd68c2015-09-23 16:45:15 +0530655 }
656
657 @Override
658 public void onAnimationStart(Animator animation) { }
659
660 @Override
661 public void onAnimationEnd(Animator animation) {
662 if (animation == mScaleUpAnimator || animation == mSuccessAnimatorSet ||
663 animation == mFadeInAnimator) {
664 // These all indicate the end of the animation
665 dismiss();
666 } else if (animation == mFastSendAnimator) {
667 // After sending is done and we've faded out, reset the scale to 1
668 // so we can fade it back in.
669 mScreenshotView.setScaleX(1.0f);
670 mScreenshotView.setScaleY(1.0f);
671 } else if (animation == mPreAnimator) {
672 if (mHardwareAccelerated && (mState == STATE_W4_TOUCH || mState == STATE_W4_NFC_TAP)) {
673 mFireflyRenderer.start(mSurface, mSurfaceWidth, mSurfaceHeight);
674 }
675 }
676 }
677
678 @Override
679 public void onAnimationCancel(Animator animation) { }
680
681 @Override
682 public void onAnimationRepeat(Animator animation) { }
683
684 @Override
685 public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
686 // This gets called on animation vsync
687 if (++mRenderedFrames < 4) {
688 // For the first 3 frames, call invalidate(); this calls eglSwapBuffers
689 // on the surface, which will allocate large buffers the first three calls
690 // as Android uses triple buffering.
691 mScreenshotLayout.invalidate();
692 } else {
693 // Buffers should be allocated, start the real animation
694 mFrameCounterAnimator.cancel();
695 mPreAnimator.start();
696 }
697 }
698
699 @Override
700 public boolean onTouch(View v, MotionEvent event) {
701 if (mState != STATE_W4_TOUCH) {
702 return false;
703 }
704 mState = STATE_SENDING;
705 // Ignore future touches
706 mScreenshotView.setOnTouchListener(null);
707
708 // Cancel any ongoing animations
709 mFrameCounterAnimator.cancel();
710 mPreAnimator.cancel();
711
712 mCallback.onSendConfirmed();
713 return true;
714 }
715
716 @Override
717 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
718 if (mHardwareAccelerated && mState < STATE_COMPLETE) {
719 mRenderedFrames = 0;
720
721 mFrameCounterAnimator.start();
722 mSurface = surface;
723 mSurfaceWidth = width;
724 mSurfaceHeight = height;
725 }
726 }
727
728 @Override
729 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
730 // Since we've disabled orientation changes, we can safely ignore this
731 }
732
733 @Override
734 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
735 mSurface = null;
736
737 return true;
738 }
739
740 @Override
741 public void onSurfaceTextureUpdated(SurfaceTexture surface) { }
742
743 public void showSendHint() {
744 if (mAlphaDownAnimator.isRunning()) {
745 mAlphaDownAnimator.cancel();
746 }
747 if (mSlowSendAnimator.isRunning()) {
748 mSlowSendAnimator.cancel();
749 }
750 mBlackLayer.setScaleX(mScreenshotView.getScaleX());
751 mBlackLayer.setScaleY(mScreenshotView.getScaleY());
752 mBlackLayer.setVisibility(View.VISIBLE);
753 mTextHint.setVisibility(View.GONE);
754
755 mTextRetry.setText(mContext.getResources().getString(R.string.beam_try_again));
756 mTextRetry.setVisibility(View.VISIBLE);
757
758 PropertyValuesHolder alphaUp = PropertyValuesHolder.ofFloat("alpha",
759 new float[] {mBlackLayer.getAlpha(), 0.9f});
760 mAlphaUpAnimator.setValues(alphaUp);
761 mAlphaUpAnimator.start();
762 }
763
764 @Override
765 public boolean dispatchKeyEvent(KeyEvent event) {
766 int keyCode = event.getKeyCode();
767 if (keyCode == KeyEvent.KEYCODE_BACK) {
768 mCallback.onCanceled();
769 return true;
770 } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
771 keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
772 // Treat as if it's a touch event
773 return onTouch(mScreenshotView, null);
774 } else {
775 return false;
776 }
777 }
778
779 @Override
780 public boolean dispatchKeyShortcutEvent(KeyEvent event) {
781 return false;
782 }
783
784 @Override
785 public boolean dispatchTouchEvent(MotionEvent event) {
786 return mScreenshotLayout.dispatchTouchEvent(event);
787 }
788
789 @Override
790 public boolean dispatchTrackballEvent(MotionEvent event) {
791 return false;
792 }
793
794 @Override
795 public boolean dispatchGenericMotionEvent(MotionEvent event) {
796 return false;
797 }
798
799 @Override
800 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
801 return false;
802 }
803
804 @Override
805 public View onCreatePanelView(int featureId) {
806 return null;
807 }
808
809 @Override
810 public boolean onCreatePanelMenu(int featureId, Menu menu) {
811 return false;
812 }
813
814 @Override
815 public boolean onPreparePanel(int featureId, View view, Menu menu) {
816 return false;
817 }
818
819 @Override
820 public boolean onMenuOpened(int featureId, Menu menu) {
821 return false;
822 }
nxpandroid281eb922016-08-25 20:27:46 +0530823
nxpandroid64fd68c2015-09-23 16:45:15 +0530824 @Override
825 public boolean onMenuItemSelected(int featureId, MenuItem item) {
826 return false;
827 }
828
829 @Override
830 public void onWindowAttributesChanged(LayoutParams attrs) {
831 }
832
833 @Override
834 public void onContentChanged() {
835 }
836
837 @Override
838 public void onWindowFocusChanged(boolean hasFocus) {
839 }
840
841 @Override
842 public void onAttachedToWindow() {
843
844 }
845
846 @Override
847 public void onDetachedFromWindow() {
848 }
849
850 @Override
851 public void onPanelClosed(int featureId, Menu menu) {
852
853 }
nxpandroid281eb922016-08-25 20:27:46 +0530854
nxpandroid1153eb32015-11-06 18:46:58 +0530855 @Override
856 public boolean onSearchRequested(SearchEvent searchEvent) {
nxpandroid281eb922016-08-25 20:27:46 +0530857 return onSearchRequested();
nxpandroid1153eb32015-11-06 18:46:58 +0530858 }
nxpandroid64fd68c2015-09-23 16:45:15 +0530859
860 @Override
861 public boolean onSearchRequested() {
862 return false;
863 }
864
865 @Override
866 public ActionMode onWindowStartingActionMode(
867 android.view.ActionMode.Callback callback) {
868 return null;
869 }
870
nxpandroid1153eb32015-11-06 18:46:58 +0530871 public ActionMode onWindowStartingActionMode(
872 android.view.ActionMode.Callback callback, int type) {
873 return null;
874 }
875
nxpandroid64fd68c2015-09-23 16:45:15 +0530876 @Override
877 public void onActionModeStarted(ActionMode mode) {
878 }
879
880 @Override
881 public void onActionModeFinished(ActionMode mode) {
882 }
nxpandroidb7819802017-09-14 11:55:40 +0530883
884 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
885 @Override
886 public void onReceive(Context context, Intent intent) {
887 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
888 mCallback.onCanceled();
889 }
890 }
891 };
nxpandroid64fd68c2015-09-23 16:45:15 +0530892}