1 /*
2  * Copyright (C) 2020 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 
17 package com.example.android.startingwindow;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.AnimatorSet;
22 import android.animation.ObjectAnimator;
23 import android.animation.ValueAnimator;
24 import android.annotation.SuppressLint;
25 import android.app.Activity;
26 import android.content.ComponentName;
27 import android.content.Intent;
28 import android.content.pm.ShortcutInfo;
29 import android.content.pm.ShortcutManager;
30 import android.content.res.Resources;
31 import android.os.Build.VERSION_CODES;
32 import android.os.Bundle;
33 import android.os.Handler;
34 import android.os.Looper;
35 import android.os.Process;
36 import android.os.SystemClock;
37 import android.provider.Settings.Global;
38 import android.util.TypedValue;
39 import android.view.View;
40 import android.view.ViewGroup;
41 import android.view.ViewTreeObserver.OnPreDrawListener;
42 import android.view.animation.AccelerateInterpolator;
43 import android.view.animation.Interpolator;
44 import android.view.animation.PathInterpolator;
45 import android.widget.Button;
46 import android.widget.LinearLayout;
47 import android.widget.LinearLayout.LayoutParams;
48 import android.window.SplashScreen;
49 import android.window.SplashScreenView;
50 
51 import androidx.annotation.RequiresApi;
52 
53 import java.time.Instant;
54 import java.time.temporal.ChronoUnit;
55 import java.util.Collections;
56 
57 @RequiresApi(api = VERSION_CODES.S)
58 public class CustomizeExitActivity extends Activity {
59 
60   public static final Interpolator EASE_IN_OUT = new PathInterpolator(.48f, .11f, .53f, .87f);
61   public static final Interpolator ACCELERATE = new AccelerateInterpolator();
62   public static final int MOCK_DELAY = 200;
63   public static final int MARGIN_ANIMATION_DURATION = 800;
64   public static final int SPLASHSCREEN_ALPHA_ANIMATION_DURATION = 500;
65   public static final int SPLASHSCREEN_TY_ANIMATION_DURATION = 1000;
66   public static final boolean WAIT_FOR_AVD_TO_FINISH = true;
67   public static final boolean DEBUG = false;
68 
69   boolean appReady = false;
70   private float animationScale = 1.0f;
71 
72   @Override
onCreate(Bundle savedInstanceState)73   protected void onCreate(Bundle savedInstanceState) {
74     super.onCreate(savedInstanceState);
75     setContentView(R.layout.main_activity);
76     getWindow().setDecorFitsSystemWindows(false);
77 
78     // On Android S, this new method has been added to Activity
79     SplashScreen splashScreen = getSplashScreen();
80 
81     // Setting an OnExitAnimationListener on the SplashScreen indicates
82     // to the system that the application will handle the exit animation.
83     // This means that the SplashScreen will be inflated in the application
84     // process once the process has started.
85     // Otherwise, the splashscreen stays in the SystemUI process and will be
86     // dismissed once the first frame of the app is drawn
87     splashScreen.setOnExitAnimationListener(this::onSplashScreenExit);
88 
89     animationScale = Global.getFloat(getContentResolver(),
90         Global.ANIMATOR_DURATION_SCALE, 1.0f);
91 
92     // Create some artificial delay to simulate some local database fetch for example
93     new Handler(Looper.getMainLooper())
94         .postDelayed(() -> appReady = true, (long) (MOCK_DELAY * animationScale));
95 
96     // We use a pre draw listener to delay the removal of the splashscreen
97     // until our app is ready
98     final View content = findViewById(android.R.id.content);
99     content.getViewTreeObserver().addOnPreDrawListener(
100         new OnPreDrawListener() {
101           @Override
102           public boolean onPreDraw() {
103             if (appReady) {
104               content.getViewTreeObserver().removeOnPreDrawListener(this);
105               return true;
106             }
107             return false;
108           }
109         }
110     );
111   }
112 
onSplashScreenExit(SplashScreenView view)113   private void onSplashScreenExit(SplashScreenView view) {
114     // At this point the first frame of the application is drawn and
115     // the SplashScreen is ready to be removed.
116 
117     // It is now up to the application to animate the provided view
118     // since the listener is registered
119     AnimatorSet animatorSet = new AnimatorSet();
120     animatorSet.setDuration(500);
121 
122     ObjectAnimator translationY = ObjectAnimator.ofFloat(view, "translationY", 0, view.getHeight());
123     translationY.setInterpolator(ACCELERATE);
124 
125     ObjectAnimator alpha = ObjectAnimator.ofFloat(view, "alpha", 1, 0);
126     alpha.setInterpolator(ACCELERATE);
127 
128     // To get fancy, we'll also animate our content
129     ValueAnimator marginAnimator = createContentAnimation();
130 
131     animatorSet.playTogether(translationY, alpha, marginAnimator);
132     animatorSet.addListener(new AnimatorListenerAdapter() {
133       @Override
134       public void onAnimationEnd(Animator animation) {
135         view.remove();
136       }
137     });
138 
139     // If we want to wait for our Animated Vector Drawable to finish animating, we can compute
140     // the remaining time to delay the start of the exit animation
141     long delayMillis = Instant.now()
142           .until(view.getIconAnimationStart().plus(view.getIconAnimationDuration()),
143               ChronoUnit.MILLIS);
144     view.postDelayed(animatorSet::start, (long) (delayMillis * animationScale));
145   }
146 
createContentAnimation()147   private ValueAnimator createContentAnimation() {
148     Resources r = getResources();
149     float marginStart = TypedValue.applyDimension(
150         TypedValue.COMPLEX_UNIT_DIP, 30, r.getDisplayMetrics()
151     );
152 
153     float marginEnd = TypedValue.applyDimension(
154         TypedValue.COMPLEX_UNIT_DIP, 10, r.getDisplayMetrics()
155     );
156 
157     ValueAnimator marginAnimator = ValueAnimator.ofFloat(marginStart, marginEnd);
158     marginAnimator.addUpdateListener(valueAnimator -> {
159       LinearLayout container = findViewById(R.id.container);
160       int marginTop = Math.round((Float) valueAnimator.getAnimatedValue());
161       for (int i = 0; i < container.getChildCount(); i++) {
162         View child = container.getChildAt(i);
163         ((LayoutParams) child.getLayoutParams()).setMargins(0, marginTop, 0, 0);
164       }
165       container.requestLayout();
166     });
167     marginAnimator.setInterpolator(EASE_IN_OUT);
168     return marginAnimator;
169   }
170 
171   @SuppressLint("SetTextI18n")
createShortcutButton()172   private void createShortcutButton() {
173     final Button Button = new Button(this);
174     Button.setText("Create shortcut");
175     Button.setOnClickListener((v) -> createShortcut());
176     addContentView(Button, new ViewGroup.LayoutParams(
177         ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
178   }
179 
createShortcut()180   private void createShortcut() {
181     ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
182     String shortcutId1 = "shortcutId1";
183     shortcutManager.removeDynamicShortcuts(Collections.singletonList(shortcutId1));
184     final ShortcutInfo.Builder b = new ShortcutInfo.Builder(this, shortcutId1);
185     final ComponentName name = new ComponentName("com.example.android.startingwindow",
186         "com.example.android.startingwindow.SecondActivity");
187     final Intent i = new Intent(Intent.ACTION_MAIN)
188         .setComponent(name);
189     ShortcutInfo shortcut = b.setShortLabel("label")
190         .setLongLabel("Long label")
191         .setIntent(i)
192         .setStartingTheme(android.R.style.Theme_Black_NoTitleBar_Fullscreen)
193         .build();
194     shortcutManager.addDynamicShortcuts(Collections.singletonList(shortcut));
195   }
196 
197   @Override
onPause()198   protected void onPause() {
199     // For the sake of this demo app, we kill the app on pause so
200     // we see a cold start animation for each launch
201     super.onPause();
202     finishAndRemoveTask();
203     Process.killProcess(Process.myPid());
204   }
205 }