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