• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.qsb;
18 
19 import android.app.Activity;
20 import android.app.Fragment;
21 import android.app.SearchManager;
22 import android.appwidget.AppWidgetHost;
23 import android.appwidget.AppWidgetHostView;
24 import android.appwidget.AppWidgetManager;
25 import android.appwidget.AppWidgetProviderInfo;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.graphics.Rect;
30 import android.os.Bundle;
31 import android.util.AttributeSet;
32 import android.view.LayoutInflater;
33 import android.view.View;
34 import android.view.ViewGroup;
35 import android.widget.FrameLayout;
36 
37 import com.android.launcher3.AppWidgetResizeFrame;
38 import com.android.launcher3.InvariantDeviceProfile;
39 import com.android.launcher3.LauncherAppState;
40 import com.android.launcher3.R;
41 import com.android.launcher3.Utilities;
42 import com.android.launcher3.config.FeatureFlags;
43 
44 /**
45  * A frame layout which contains a QSB. This internally uses fragment to bind the view, which
46  * allows it to contain the logic for {@link Fragment#startActivityForResult(Intent, int)}.
47  *
48  * Note: AppWidgetManagerCompat can be disabled using FeatureFlags. In QSB, we should use
49  * AppWidgetManager directly, so that it keeps working in that case.
50  */
51 public class QsbContainerView extends FrameLayout {
52 
QsbContainerView(Context context)53     public QsbContainerView(Context context) {
54         super(context);
55     }
56 
QsbContainerView(Context context, AttributeSet attrs)57     public QsbContainerView(Context context, AttributeSet attrs) {
58         super(context, attrs);
59     }
60 
QsbContainerView(Context context, AttributeSet attrs, int defStyleAttr)61     public QsbContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
62         super(context, attrs, defStyleAttr);
63     }
64 
65     @Override
setPadding(int left, int top, int right, int bottom)66     public void setPadding(int left, int top, int right, int bottom) {
67         super.setPadding(0, 0, 0, 0);
68     }
69 
setPaddingUnchecked(int left, int top, int right, int bottom)70     protected void setPaddingUnchecked(int left, int top, int right, int bottom) {
71         super.setPadding(left, top, right, bottom);
72     }
73 
74     /**
75      * A fragment to display the QSB.
76      */
77     public static class QsbFragment extends Fragment implements View.OnClickListener {
78 
79         private static final int REQUEST_BIND_QSB = 1;
80         private static final String QSB_WIDGET_ID = "qsb_widget_id";
81 
82         private QsbWidgetHost mQsbWidgetHost;
83         private AppWidgetProviderInfo mWidgetInfo;
84         private QsbWidgetHostView mQsb;
85 
86         // We need to store the orientation here, due to a bug (b/64916689) that results in widgets
87         // being inflated in the wrong orientation.
88         private int mOrientation;
89 
90         @Override
onCreate(Bundle savedInstanceState)91         public void onCreate(Bundle savedInstanceState) {
92             super.onCreate(savedInstanceState);
93             mQsbWidgetHost = new QsbWidgetHost(getActivity());
94             mOrientation = getContext().getResources().getConfiguration().orientation;
95         }
96 
97         private FrameLayout mWrapper;
98 
99         @Override
onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)100         public View onCreateView(
101                 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
102 
103             mWrapper = new FrameLayout(getActivity());
104 
105             // Only add the view when enabled
106             if (isQsbEnabled()) {
107                 mWrapper.addView(createQsb(mWrapper));
108             }
109             return mWrapper;
110         }
111 
createQsb(ViewGroup container)112         private View createQsb(ViewGroup container) {
113             Activity activity = getActivity();
114             mWidgetInfo = getSearchWidgetProvider(activity);
115             if (mWidgetInfo == null) {
116                 // There is no search provider, just show the default widget.
117                 return QsbWidgetHostView.getDefaultView(container);
118             }
119 
120             AppWidgetManager widgetManager = AppWidgetManager.getInstance(activity);
121             InvariantDeviceProfile idp = LauncherAppState.getIDP(activity);
122 
123             Bundle opts = new Bundle();
124             Rect size = AppWidgetResizeFrame.getWidgetSizeRanges(activity, idp.numColumns, 1, null);
125             opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, size.left);
126             opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, size.top);
127             opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, size.right);
128             opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, size.bottom);
129 
130             int widgetId = Utilities.getPrefs(activity).getInt(QSB_WIDGET_ID, -1);
131             AppWidgetProviderInfo widgetInfo = widgetManager.getAppWidgetInfo(widgetId);
132             boolean isWidgetBound = (widgetInfo != null) &&
133                     widgetInfo.provider.equals(mWidgetInfo.provider);
134 
135             int oldWidgetId = widgetId;
136             if (!isWidgetBound) {
137                 if (widgetId > -1) {
138                     // widgetId is already bound and its not the correct provider. reset host.
139                     mQsbWidgetHost.deleteHost();
140                 }
141 
142                 widgetId = mQsbWidgetHost.allocateAppWidgetId();
143                 isWidgetBound = widgetManager.bindAppWidgetIdIfAllowed(
144                         widgetId, mWidgetInfo.getProfile(), mWidgetInfo.provider, opts);
145                 if (!isWidgetBound) {
146                     mQsbWidgetHost.deleteAppWidgetId(widgetId);
147                     widgetId = -1;
148                 }
149 
150                 if (oldWidgetId != widgetId) {
151                     saveWidgetId(widgetId);
152                 }
153             }
154 
155             if (isWidgetBound) {
156                 mQsb = (QsbWidgetHostView) mQsbWidgetHost.createView(activity, widgetId, mWidgetInfo);
157                 mQsb.setId(R.id.qsb_widget);
158 
159                 if (!Utilities.containsAll(AppWidgetManager.getInstance(activity)
160                         .getAppWidgetOptions(widgetId), opts)) {
161                     mQsb.updateAppWidgetOptions(opts);
162                 }
163                 mQsb.setPadding(0, 0, 0, 0);
164                 mQsbWidgetHost.startListening();
165                 return mQsb;
166             }
167 
168             // Return a default widget with setup icon.
169             View v = QsbWidgetHostView.getDefaultView(container);
170             View setupButton = v.findViewById(R.id.btn_qsb_setup);
171             setupButton.setVisibility(View.VISIBLE);
172             setupButton.setOnClickListener(this);
173             return v;
174         }
175 
saveWidgetId(int widgetId)176         private void saveWidgetId(int widgetId) {
177             Utilities.getPrefs(getActivity()).edit().putInt(QSB_WIDGET_ID, widgetId).apply();
178         }
179 
180         @Override
onClick(View view)181         public void onClick(View view) {
182             // Start intent for bind the widget
183             Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
184             // Allocate a new widget id for QSB
185             intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mQsbWidgetHost.allocateAppWidgetId());
186             intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, mWidgetInfo.provider);
187             startActivityForResult(intent, REQUEST_BIND_QSB);
188         }
189 
190         @Override
onActivityResult(int requestCode, int resultCode, Intent data)191         public void onActivityResult(int requestCode, int resultCode, Intent data) {
192             if (requestCode == REQUEST_BIND_QSB) {
193                 if (resultCode == Activity.RESULT_OK) {
194                     saveWidgetId(data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1));
195                     rebindFragment();
196                 } else {
197                     mQsbWidgetHost.deleteHost();
198                 }
199             }
200         }
201 
202         @Override
onResume()203         public void onResume() {
204             super.onResume();
205             if (mQsb != null && mQsb.isReinflateRequired(mOrientation)) {
206                 rebindFragment();
207             }
208         }
209 
210         @Override
onDestroy()211         public void onDestroy() {
212             mQsbWidgetHost.stopListening();
213             super.onDestroy();
214         }
215 
rebindFragment()216         private void rebindFragment() {
217             // Exit if the embedded qsb is disabled
218             if (!isQsbEnabled()) {
219                 return;
220             }
221 
222             if (mWrapper != null && getActivity() != null) {
223                 mWrapper.removeAllViews();
224                 mWrapper.addView(createQsb(mWrapper));
225             }
226         }
227 
isQsbEnabled()228         public boolean isQsbEnabled() {
229             return FeatureFlags.QSB_ON_FIRST_SCREEN;
230         }
231     }
232 
233     /**
234      * Returns a widget with category {@link AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX}
235      * provided by the same package which is set to be global search activity.
236      * If widgetCategory is not supported, or no such widget is found, returns the first widget
237      * provided by the package.
238      */
getSearchWidgetProvider(Context context)239     public static AppWidgetProviderInfo getSearchWidgetProvider(Context context) {
240         SearchManager searchManager =
241                 (SearchManager) context.getSystemService(Context.SEARCH_SERVICE);
242         ComponentName searchComponent = searchManager.getGlobalSearchActivity();
243         if (searchComponent == null) return null;
244         String providerPkg = searchComponent.getPackageName();
245 
246         AppWidgetProviderInfo defaultWidgetForSearchPackage = null;
247 
248         AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
249         for (AppWidgetProviderInfo info : appWidgetManager.getInstalledProviders()) {
250             if (info.provider.getPackageName().equals(providerPkg) && info.configure == null) {
251                 if ((info.widgetCategory & AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX) != 0) {
252                     return info;
253                 } else if (defaultWidgetForSearchPackage == null) {
254                     defaultWidgetForSearchPackage = info;
255                 }
256             }
257         }
258         return defaultWidgetForSearchPackage;
259     }
260 
261     private static class QsbWidgetHost extends AppWidgetHost {
262 
263         private static final int QSB_WIDGET_HOST_ID = 1026;
264 
QsbWidgetHost(Context context)265         public QsbWidgetHost(Context context) {
266             super(context, QSB_WIDGET_HOST_ID);
267         }
268 
269         @Override
onCreateView( Context context, int appWidgetId, AppWidgetProviderInfo appWidget)270         protected AppWidgetHostView onCreateView(
271                 Context context, int appWidgetId, AppWidgetProviderInfo appWidget) {
272             return new QsbWidgetHostView(context);
273         }
274     }
275 }
276