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.android.launcher3.graphics;
18 
19 import static com.android.launcher3.config.FeatureFlags.MULTI_DB_GRID_MIRATION_ALGO;
20 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
21 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
22 
23 import android.content.Context;
24 import android.hardware.display.DisplayManager;
25 import android.os.Bundle;
26 import android.os.Handler;
27 import android.os.IBinder;
28 import android.os.Looper;
29 import android.os.Message;
30 import android.os.Messenger;
31 import android.view.Display;
32 import android.view.SurfaceControlViewHost;
33 import android.view.View;
34 import android.view.animation.AccelerateDecelerateInterpolator;
35 
36 import com.android.launcher3.InvariantDeviceProfile;
37 import com.android.launcher3.model.GridSizeMigrationTask;
38 import com.android.launcher3.model.GridSizeMigrationTaskV2;
39 
40 import java.util.concurrent.TimeUnit;
41 
42 /** Render preview using surface view. */
43 public class PreviewSurfaceRenderer implements IBinder.DeathRecipient {
44 
45     private static final int FADE_IN_ANIMATION_DURATION = 200;
46 
47     private static final String KEY_HOST_TOKEN = "host_token";
48     private static final String KEY_VIEW_WIDTH = "width";
49     private static final String KEY_VIEW_HEIGHT = "height";
50     private static final String KEY_DISPLAY_ID = "display_id";
51     private static final String KEY_SURFACE_PACKAGE = "surface_package";
52     private static final String KEY_CALLBACK = "callback";
53 
54     private final Context mContext;
55     private final InvariantDeviceProfile mIdp;
56     private final IBinder mHostToken;
57     private final int mWidth;
58     private final int mHeight;
59     private final Display mDisplay;
60 
61     private SurfaceControlViewHost mSurfaceControlViewHost;
62 
PreviewSurfaceRenderer(Context context, Bundle bundle)63     PreviewSurfaceRenderer(Context context, Bundle bundle) {
64         mContext = context;
65 
66         String gridName = bundle.getString("name");
67         bundle.remove("name");
68         if (gridName == null) {
69             gridName = InvariantDeviceProfile.getCurrentGridName(context);
70         }
71         mIdp = new InvariantDeviceProfile(context, gridName);
72 
73         mHostToken = bundle.getBinder(KEY_HOST_TOKEN);
74         mWidth = bundle.getInt(KEY_VIEW_WIDTH);
75         mHeight = bundle.getInt(KEY_VIEW_HEIGHT);
76 
77         final DisplayManager displayManager = (DisplayManager) context.getSystemService(
78                 Context.DISPLAY_SERVICE);
79         mDisplay = displayManager.getDisplay(bundle.getInt(KEY_DISPLAY_ID));
80     }
81 
82     /** Handle a received surface view request. */
render()83     Bundle render() {
84         if (mSurfaceControlViewHost != null) {
85             binderDied();
86         }
87 
88         SurfaceControlViewHost.SurfacePackage surfacePackage;
89         try {
90             mSurfaceControlViewHost = MAIN_EXECUTOR
91                     .submit(() -> new SurfaceControlViewHost(mContext, mDisplay, mHostToken))
92                     .get(5, TimeUnit.SECONDS);
93             surfacePackage = mSurfaceControlViewHost.getSurfacePackage();
94             mHostToken.linkToDeath(this, 0);
95         } catch (Exception e) {
96             e.printStackTrace();
97             return null;
98         }
99 
100         MODEL_EXECUTOR.post(() -> {
101             final boolean success = doGridMigrationIfNecessary();
102 
103             MAIN_EXECUTOR.post(() -> {
104                 // If mSurfaceControlViewHost is null due to any reason (e.g. binder died,
105                 // happening when user leaves the preview screen before preview rendering finishes),
106                 // we should return here.
107                 SurfaceControlViewHost host = mSurfaceControlViewHost;
108                 if (host == null) {
109                     return;
110                 }
111 
112                 View view = new LauncherPreviewRenderer(mContext, mIdp, success).getRenderedView();
113                 // This aspect scales the view to fit in the surface and centers it
114                 final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(),
115                         mHeight / (float) view.getMeasuredHeight());
116                 view.setScaleX(scale);
117                 view.setScaleY(scale);
118                 view.setPivotX(0);
119                 view.setPivotY(0);
120                 view.setTranslationX((mWidth - scale * view.getWidth()) / 2);
121                 view.setTranslationY((mHeight - scale * view.getHeight()) / 2);
122                 view.setAlpha(0);
123                 view.animate().alpha(1)
124                         .setInterpolator(new AccelerateDecelerateInterpolator())
125                         .setDuration(FADE_IN_ANIMATION_DURATION)
126                         .start();
127                 host.setView(view, view.getMeasuredWidth(), view.getMeasuredHeight());
128             });
129         });
130 
131         Bundle result = new Bundle();
132         result.putParcelable(KEY_SURFACE_PACKAGE, surfacePackage);
133 
134         Handler handler = new Handler(Looper.getMainLooper(), message -> {
135             binderDied();
136             return true;
137         });
138         Messenger messenger = new Messenger(handler);
139         Message msg = Message.obtain();
140         msg.replyTo = messenger;
141         result.putParcelable(KEY_CALLBACK, msg);
142         return result;
143     }
144 
145     @Override
binderDied()146     public void binderDied() {
147         if (mSurfaceControlViewHost != null) {
148             MAIN_EXECUTOR.execute(() -> {
149                 mSurfaceControlViewHost.release();
150                 mSurfaceControlViewHost = null;
151             });
152         }
153         mHostToken.unlinkToDeath(this, 0);
154     }
155 
doGridMigrationIfNecessary()156     private boolean doGridMigrationIfNecessary() {
157         boolean needsToMigrate =
158                 MULTI_DB_GRID_MIRATION_ALGO.get()
159                         ? GridSizeMigrationTaskV2.needsToMigrate(mContext, mIdp)
160                         : GridSizeMigrationTask.needsToMigrate(mContext, mIdp);
161         if (!needsToMigrate) {
162             return false;
163         }
164         return MULTI_DB_GRID_MIRATION_ALGO.get()
165                 ? GridSizeMigrationTaskV2.migrateGridIfNeeded(mContext, mIdp)
166                 : GridSizeMigrationTask.migrateGridIfNeeded(mContext, mIdp);
167     }
168 }
169