• 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;
18 
19 import android.app.Activity;
20 import android.app.Fragment;
21 import android.app.SearchManager;
22 import android.appwidget.AppWidgetManager;
23 import android.appwidget.AppWidgetProviderInfo;
24 import android.content.BroadcastReceiver;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.content.SharedPreferences;
30 import android.graphics.Rect;
31 import android.os.Bundle;
32 import android.util.AttributeSet;
33 import android.view.LayoutInflater;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.widget.FrameLayout;
37 
38 import com.android.launcher3.compat.AppWidgetManagerCompat;
39 
40 /**
41  * A frame layout which contains a QSB. This internally uses fragment to bind the view, which
42  * allows it to contain the logic for {@link Fragment#startActivityForResult(Intent, int)}.
43  */
44 public class QsbContainerView extends FrameLayout {
45 
QsbContainerView(Context context)46     public QsbContainerView(Context context) {
47         super(context);
48     }
49 
QsbContainerView(Context context, AttributeSet attrs)50     public QsbContainerView(Context context, AttributeSet attrs) {
51         super(context, attrs);
52     }
53 
QsbContainerView(Context context, AttributeSet attrs, int defStyleAttr)54     public QsbContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
55         super(context, attrs, defStyleAttr);
56     }
57 
58     @Override
setPadding(int left, int top, int right, int bottom)59     public void setPadding(int left, int top, int right, int bottom) {
60         super.setPadding(0, 0, 0, 0);
61     }
62 
63     /**
64      * A fragment to display the QSB.
65      */
66     public static class QsbFragment extends Fragment implements View.OnClickListener {
67 
68         private static final int REQUEST_BIND_QSB = 1;
69         private static final String QSB_WIDGET_ID = "qsb_widget_id";
70 
71         private static int sSavedWidgetId = -1;
72 
73         private AppWidgetProviderInfo mWidgetInfo;
74         private LauncherAppWidgetHostView mQsb;
75 
76         private BroadcastReceiver mRebindReceiver = new BroadcastReceiver() {
77             @Override
78             public void onReceive(Context context, Intent intent) {
79                 rebindFragment();
80             }
81         };
82 
83         @Override
onCreate(Bundle savedInstanceState)84         public void onCreate(Bundle savedInstanceState) {
85             super.onCreate(savedInstanceState);
86 
87             IntentFilter filter = new IntentFilter(Launcher.ACTION_APPWIDGET_HOST_RESET);
88             filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
89             getActivity().registerReceiver(mRebindReceiver, filter);
90         }
91 
92         private FrameLayout mWrapper;
93 
94         @Override
onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)95         public View onCreateView(
96                 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
97 
98             if (savedInstanceState != null) {
99                 sSavedWidgetId = savedInstanceState.getInt(QSB_WIDGET_ID, -1);
100             }
101             mWrapper = new FrameLayout(getActivity());
102             mWrapper.addView(createQsb(inflater, mWrapper));
103             return mWrapper;
104         }
105 
createQsb(LayoutInflater inflater, ViewGroup container)106         private View createQsb(LayoutInflater inflater, ViewGroup container) {
107             Launcher launcher = Launcher.getLauncher(getActivity());
108             mWidgetInfo = getSearchWidgetProvider(launcher);
109             if (mWidgetInfo == null) {
110                 // There is no search provider, just show the default widget.
111                 return getDefaultView(inflater, container, false);
112             }
113 
114             SharedPreferences prefs = Utilities.getPrefs(launcher);
115             AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(launcher);
116             LauncherAppWidgetHost widgetHost = launcher.getAppWidgetHost();
117             InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile();
118 
119             Bundle opts = new Bundle();
120             Rect size = AppWidgetResizeFrame.getWidgetSizeRanges(launcher, idp.numColumns, 1, null);
121             opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, size.left);
122             opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, size.top);
123             opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, size.right);
124             opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, size.bottom);
125 
126             int widgetId = prefs.getInt(QSB_WIDGET_ID, -1);
127             AppWidgetProviderInfo widgetInfo = widgetManager.getAppWidgetInfo(widgetId);
128             boolean isWidgetBound = (widgetInfo != null) &&
129                     widgetInfo.provider.equals(mWidgetInfo.provider);
130 
131             if (!isWidgetBound) {
132                 // widgetId is already bound and its not the correct provider.
133                 // Delete the widget id.
134                 if (widgetId > -1) {
135                     widgetHost.deleteAppWidgetId(widgetId);
136                     widgetId = -1;
137                 }
138 
139                 widgetId = widgetHost.allocateAppWidgetId();
140                 isWidgetBound = widgetManager.bindAppWidgetIdIfAllowed(widgetId, mWidgetInfo, opts);
141                 if (!isWidgetBound) {
142                     widgetHost.deleteAppWidgetId(widgetId);
143                     widgetId = -1;
144                 }
145             }
146 
147             if (isWidgetBound) {
148                 mQsb = (LauncherAppWidgetHostView)
149                         widgetHost.createView(launcher, widgetId, mWidgetInfo);
150                 mQsb.setId(R.id.qsb_widget);
151                 mQsb.mErrorViewId = R.layout.qsb_default_view;
152 
153                 if (!Utilities.containsAll(AppWidgetManager.getInstance(launcher)
154                         .getAppWidgetOptions(widgetId), opts)) {
155                     mQsb.updateAppWidgetOptions(opts);
156                 }
157                 mQsb.setPadding(0, 0, 0, 0);
158                 return mQsb;
159             }
160 
161             // Return a default widget with setup icon.
162             return getDefaultView(inflater, container, true);
163         }
164 
165         @Override
onClick(View view)166         public void onClick(View view) {
167             if (view.getId() == R.id.btn_qsb_search) {
168                 getActivity().startSearch("", false, null, true);
169             } else if (view.getId() == R.id.btn_qsb_setup) {
170                 // Allocate a new widget id for QSB
171                 sSavedWidgetId = Launcher.getLauncher(getActivity())
172                         .getAppWidgetHost().allocateAppWidgetId();
173                 // Start intent for bind the widget
174                 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
175                 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, sSavedWidgetId);
176                 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, mWidgetInfo.provider);
177                 startActivityForResult(intent, REQUEST_BIND_QSB);
178             }
179         }
180 
181         @Override
onSaveInstanceState(Bundle outState)182         public void onSaveInstanceState(Bundle outState) {
183             super.onSaveInstanceState(outState);
184             outState.putInt(QSB_WIDGET_ID, sSavedWidgetId);
185         }
186 
187         @Override
onActivityResult(int requestCode, int resultCode, Intent data)188         public void onActivityResult(int requestCode, int resultCode, Intent data) {
189             if (requestCode == REQUEST_BIND_QSB) {
190                 if (resultCode == Activity.RESULT_OK) {
191                     int widgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
192                             sSavedWidgetId);
193                     Utilities.getPrefs(getActivity()).edit().putInt(QSB_WIDGET_ID, widgetId).apply();
194                     sSavedWidgetId = -1;
195                     rebindFragment();
196                 } else if (sSavedWidgetId != -1) {
197                     Launcher.getLauncher(getActivity()).getAppWidgetHost()
198                             .deleteAppWidgetId(sSavedWidgetId);
199                     sSavedWidgetId = -1;
200                 }
201             }
202         }
203 
204         @Override
onResume()205         public void onResume() {
206             super.onResume();
207             if (mQsb != null && mQsb.isReinflateRequired()) {
208                 rebindFragment();
209             }
210         }
211 
212         @Override
onDestroy()213         public void onDestroy() {
214             getActivity().unregisterReceiver(mRebindReceiver);
215             super.onDestroy();
216         }
217 
rebindFragment()218         private void rebindFragment() {
219             if (mWrapper != null && getActivity() != null) {
220                 mWrapper.removeAllViews();
221                 mWrapper.addView(createQsb(getActivity().getLayoutInflater(), mWrapper));
222             }
223         }
224 
getDefaultView(LayoutInflater inflater, ViewGroup parent, boolean showSetup)225         private View getDefaultView(LayoutInflater inflater, ViewGroup parent, boolean showSetup) {
226             View v = inflater.inflate(R.layout.qsb_default_view, parent, false);
227             if (showSetup) {
228                 View setupButton = v.findViewById(R.id.btn_qsb_setup);
229                 setupButton.setVisibility(View.VISIBLE);
230                 setupButton.setOnClickListener(this);
231             }
232             v.findViewById(R.id.btn_qsb_search).setOnClickListener(this);
233             return v;
234         }
235     }
236 
237     /**
238      * Returns a widget with category {@link AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX}
239      * provided by the same package which is set to be global search activity.
240      * If widgetCategory is not supported, or no such widget is found, returns the first widget
241      * provided by the package.
242      */
getSearchWidgetProvider(Context context)243     public static AppWidgetProviderInfo getSearchWidgetProvider(Context context) {
244         SearchManager searchManager =
245                 (SearchManager) context.getSystemService(Context.SEARCH_SERVICE);
246         ComponentName searchComponent = searchManager.getGlobalSearchActivity();
247         if (searchComponent == null) return null;
248         String providerPkg = searchComponent.getPackageName();
249 
250         AppWidgetProviderInfo defaultWidgetForSearchPackage = null;
251 
252         AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
253         for (AppWidgetProviderInfo info : appWidgetManager.getInstalledProviders()) {
254             if (info.provider.getPackageName().equals(providerPkg) && info.configure == null) {
255                 if ((info.widgetCategory & AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX) != 0) {
256                     return info;
257                 } else if (defaultWidgetForSearchPackage == null) {
258                     defaultWidgetForSearchPackage = info;
259                 }
260             }
261         }
262         return defaultWidgetForSearchPackage;
263     }
264 }
265