1c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck/*
2c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * Copyright (C) 2015 The Android Open Source Project
3c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck *
4c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * Licensed under the Apache License, Version 2.0 (the "License");
5c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * you may not use this file except in compliance with the License.
6c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * You may obtain a copy of the License at
7c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck *
8c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck *      http://www.apache.org/licenses/LICENSE-2.0
9c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck *
10c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * Unless required by applicable law or agreed to in writing, software
11c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * distributed under the License is distributed on an "AS IS" BASIS,
12c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * See the License for the specific language governing permissions and
14c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * limitations under the License.
15c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck */
16c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck
17c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckpackage android.support.v4.view;
18c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck
1989f7eba0480c5c9603b295640fcd8a354be2ea20John Reckimport android.content.Context;
20c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.os.Handler;
21c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.os.Handler.Callback;
22c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.os.Looper;
23c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.os.Message;
24c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.support.annotation.LayoutRes;
25c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.support.annotation.NonNull;
26c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.support.annotation.Nullable;
27c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.support.annotation.UiThread;
28c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.support.v4.util.Pools.SynchronizedPool;
2989f7eba0480c5c9603b295640fcd8a354be2ea20John Reckimport android.util.AttributeSet;
30c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.util.Log;
31c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.view.LayoutInflater;
32c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.view.View;
33c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport android.view.ViewGroup;
34c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck
35c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckimport java.util.concurrent.ArrayBlockingQueue;
36c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck
37c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck/**
38c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * <p>Helper class for inflating layouts asynchronously. To use, construct
39c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * an instance of {@link AsyncLayoutInflater} on the UI thread and call
40c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * {@link #inflate(int, ViewGroup, OnInflateFinishedListener)}. The
41c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * {@link OnInflateFinishedListener} will be invoked on the UI thread
42c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * when the inflate request has completed.
43c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck *
44c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * <p>This is intended for parts of the UI that are created lazily or in
45c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * response to user interactions. This allows the UI thread to continue
46c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * to be responsive & animate while the relatively heavy inflate
47c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * is being performed.
48c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck *
49c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * <p>For a layout to be inflated asynchronously it needs to have a parent
50c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * whose {@link ViewGroup#generateLayoutParams(AttributeSet)} is thread-safe
51c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * and all the Views being constructed as part of inflation must not create
52c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * any {@link Handler}s or otherwise call {@link Looper#myLooper()}. If the
53c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * layout that is trying to be inflated cannot be constructed
54c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * asynchronously for whatever reason, {@link AsyncLayoutInflater} will
55c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * automatically fall back to inflating on the UI thread.
56c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck *
57c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * <p>NOTE that the inflated View hierarchy is NOT added to the parent. It is
58c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * equivalent to calling {@link LayoutInflater#inflate(int, ViewGroup, boolean)}
59c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * with attachToRoot set to false. Callers will likely want to call
60c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * {@link ViewGroup#addView(View)} in the {@link OnInflateFinishedListener}
61c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck * callback at a minimum.
6289f7eba0480c5c9603b295640fcd8a354be2ea20John Reck *
6389f7eba0480c5c9603b295640fcd8a354be2ea20John Reck * <p>This inflater does not support setting a {@link LayoutInflater.Factory}
6489f7eba0480c5c9603b295640fcd8a354be2ea20John Reck * nor {@link LayoutInflater.Factory2}. Similarly it does not support inflating
6589f7eba0480c5c9603b295640fcd8a354be2ea20John Reck * layouts that contain fragments.
66c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck */
67c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reckpublic final class AsyncLayoutInflater {
68c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck    private static final String TAG = "AsyncLayoutInflater";
69c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck
70c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck    private LayoutInflater mInflater;
71c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck    private Handler mHandler;
72c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck    private InflateThread mInflateThread;
73c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck
7489f7eba0480c5c9603b295640fcd8a354be2ea20John Reck    public AsyncLayoutInflater(@NonNull Context context) {
7589f7eba0480c5c9603b295640fcd8a354be2ea20John Reck        mInflater = new BasicInflater(context);
76c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        mHandler = new Handler(mHandlerCallback);
77c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        mInflateThread = InflateThread.getInstance();
78c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck    }
79c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck
80c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck    @UiThread
81c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck    public void inflate(@LayoutRes int resid, @Nullable ViewGroup parent,
82c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            @NonNull OnInflateFinishedListener callback) {
83c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        if (callback == null) {
84c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            throw new NullPointerException("callback argument may not be null!");
85c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        }
86c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        InflateRequest request = mInflateThread.obtainRequest();
87c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        request.inflater = this;
88c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        request.resid = resid;
89c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        request.parent = parent;
90c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        request.callback = callback;
91c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        mInflateThread.enqueue(request);
92c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck    }
93c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck
94c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck    private Callback mHandlerCallback = new Callback() {
95c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        @Override
96c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        public boolean handleMessage(Message msg) {
97c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            InflateRequest request = (InflateRequest) msg.obj;
98c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            if (request.view == null) {
99c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                request.view = mInflater.inflate(
100c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                        request.resid, request.parent, false);
101c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            }
102c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            request.callback.onInflateFinished(
103c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                    request.view, request.resid, request.parent);
104c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            mInflateThread.releaseRequest(request);
105c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            return true;
106c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        }
107c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck    };
108c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck
109c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck    public interface OnInflateFinishedListener {
110c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        public void onInflateFinished(View view, int resid, ViewGroup parent);
111c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck    }
112c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck
113c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck    private static class InflateRequest {
114c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        AsyncLayoutInflater inflater;
115c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        ViewGroup parent;
116c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        int resid;
117c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        View view;
118c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        OnInflateFinishedListener callback;
119c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck    }
120c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck
12189f7eba0480c5c9603b295640fcd8a354be2ea20John Reck    private static class BasicInflater extends LayoutInflater {
12289f7eba0480c5c9603b295640fcd8a354be2ea20John Reck        private static final String[] sClassPrefixList = {
12389f7eba0480c5c9603b295640fcd8a354be2ea20John Reck            "android.widget.",
12489f7eba0480c5c9603b295640fcd8a354be2ea20John Reck            "android.webkit.",
12589f7eba0480c5c9603b295640fcd8a354be2ea20John Reck            "android.app."
12689f7eba0480c5c9603b295640fcd8a354be2ea20John Reck        };
12789f7eba0480c5c9603b295640fcd8a354be2ea20John Reck
12889f7eba0480c5c9603b295640fcd8a354be2ea20John Reck        public BasicInflater(Context context) {
12989f7eba0480c5c9603b295640fcd8a354be2ea20John Reck            super(context);
13089f7eba0480c5c9603b295640fcd8a354be2ea20John Reck        }
13189f7eba0480c5c9603b295640fcd8a354be2ea20John Reck
13289f7eba0480c5c9603b295640fcd8a354be2ea20John Reck        @Override
13389f7eba0480c5c9603b295640fcd8a354be2ea20John Reck        public LayoutInflater cloneInContext(Context newContext) {
13489f7eba0480c5c9603b295640fcd8a354be2ea20John Reck            return new BasicInflater(newContext);
13589f7eba0480c5c9603b295640fcd8a354be2ea20John Reck        }
13689f7eba0480c5c9603b295640fcd8a354be2ea20John Reck
13789f7eba0480c5c9603b295640fcd8a354be2ea20John Reck        @Override
13889f7eba0480c5c9603b295640fcd8a354be2ea20John Reck        protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
13989f7eba0480c5c9603b295640fcd8a354be2ea20John Reck            for (String prefix : sClassPrefixList) {
14089f7eba0480c5c9603b295640fcd8a354be2ea20John Reck                try {
14189f7eba0480c5c9603b295640fcd8a354be2ea20John Reck                    View view = createView(name, prefix, attrs);
14289f7eba0480c5c9603b295640fcd8a354be2ea20John Reck                    if (view != null) {
14389f7eba0480c5c9603b295640fcd8a354be2ea20John Reck                        return view;
14489f7eba0480c5c9603b295640fcd8a354be2ea20John Reck                    }
14589f7eba0480c5c9603b295640fcd8a354be2ea20John Reck                } catch (ClassNotFoundException e) {
14689f7eba0480c5c9603b295640fcd8a354be2ea20John Reck                    // In this case we want to let the base class take a crack
14789f7eba0480c5c9603b295640fcd8a354be2ea20John Reck                    // at it.
14889f7eba0480c5c9603b295640fcd8a354be2ea20John Reck                }
14989f7eba0480c5c9603b295640fcd8a354be2ea20John Reck            }
15089f7eba0480c5c9603b295640fcd8a354be2ea20John Reck
15189f7eba0480c5c9603b295640fcd8a354be2ea20John Reck            return super.onCreateView(name, attrs);
15289f7eba0480c5c9603b295640fcd8a354be2ea20John Reck        }
15389f7eba0480c5c9603b295640fcd8a354be2ea20John Reck    }
15489f7eba0480c5c9603b295640fcd8a354be2ea20John Reck
155c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck    private static class InflateThread extends Thread {
156c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        private static final InflateThread sInstance;
157c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        static {
158c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            sInstance = new InflateThread();
159c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            sInstance.start();
160c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        }
161c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck
162c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        public static InflateThread getInstance() {
163c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            return sInstance;
164c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        }
165c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck
166c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        private ArrayBlockingQueue<InflateRequest> mQueue
167c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                = new ArrayBlockingQueue<>(10);
168c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        private SynchronizedPool<InflateRequest> mRequestPool
169c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                = new SynchronizedPool<>(10);
170c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck
171c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        @Override
172c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        public void run() {
173c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            while (true) {
174c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                InflateRequest request;
175c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                try {
176c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                    request = mQueue.take();
177c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                } catch (InterruptedException ex) {
178c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                    // Odd, just continue
179c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                    Log.w(TAG, ex);
180c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                    continue;
181c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                }
182c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck
183c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                try {
184c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                    request.view = request.inflater.mInflater.inflate(
185c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                            request.resid, request.parent, false);
186c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                } catch (RuntimeException ex) {
187c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                    // Probably a Looper failure, retry on the UI thread
188c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                    Log.w(TAG, "Failed to inflate resource in the background! Retrying on the UI thread",
189c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                            ex);
190c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                }
191c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                Message.obtain(request.inflater.mHandler, 0, request)
192c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                        .sendToTarget();
193c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            }
194c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        }
195c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck
196c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        public InflateRequest obtainRequest() {
197c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            InflateRequest obj = mRequestPool.acquire();
198c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            if (obj == null) {
199c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                obj = new InflateRequest();
200c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            }
201c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            return obj;
202c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        }
203c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck
204c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        public void releaseRequest(InflateRequest obj) {
205c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            obj.callback = null;
206c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            obj.inflater = null;
207c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            obj.parent = null;
208c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            obj.resid = 0;
209c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            obj.view = null;
210c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            mRequestPool.release(obj);
211c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        }
212c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck
213c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        public void enqueue(InflateRequest request) {
214c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            try {
215c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                mQueue.put(request);
216c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            } catch (InterruptedException e) {
217c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                throw new RuntimeException(
218c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck                        "Failed to enqueue async inflate request", e);
219c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck            }
220c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck        }
221c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck    }
222c98f0506eabc45bd8450c4ad0e30ffb8d97bada3John Reck}
223