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