content: /*
   * Copyright (C) 2016 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.launcher3.qsb;
  
  import android.app.Activity;
  import android.app.Fragment;
  import android.app.SearchManager;
  import android.appwidget.AppWidgetHost;
  import android.appwidget.AppWidgetHostView;
  import android.appwidget.AppWidgetManager;
  import android.appwidget.AppWidgetProviderInfo;
  import android.content.ComponentName;
  import android.content.Context;
  import android.content.Intent;
  import android.graphics.Rect;
  import android.os.Bundle;
  import android.provider.Settings;
  import android.util.AttributeSet;
  import android.view.LayoutInflater;
  import android.view.View;
  import android.view.ViewGroup;
  import android.widget.FrameLayout;
  
  import androidx.annotation.NonNull;
  import androidx.annotation.Nullable;
  
  import com.android.launcher3.AppWidgetResizeFrame;
  import com.android.launcher3.InvariantDeviceProfile;
  import com.android.launcher3.LauncherAppState;
  import com.android.launcher3.R;
  import com.android.launcher3.Utilities;
  import com.android.launcher3.config.FeatureFlags;
  import com.android.launcher3.graphics.FragmentWithPreview;
  
  import java.util.List;
  
  import static android.appwidget.AppWidgetManager.ACTION_APPWIDGET_BIND;
  import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
  import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_PROVIDER;
  import static android.content.Context.SEARCH_SERVICE;
  
  /**
   * A frame layout which contains a QSB. This internally uses fragment to bind the view, which
   * allows it to contain the logic for {@link Fragment#startActivityForResult(Intent, int)}.
   *
   * Note: AppWidgetManagerCompat can be disabled using FeatureFlags. In QSB, we should use
   * AppWidgetManager directly, so that it keeps working in that case.
   */
  public class QsbContainerView extends FrameLayout {
  
      public static final String SEARCH_PROVIDER_SETTINGS_KEY = "SEARCH_PROVIDER_PACKAGE_NAME";
  
      /**
       * Returns the package name for user configured search provider or from searchManager
       * @param context
       * @return String
       */
      @Nullable
      public static String getSearchWidgetPackageName(@NonNull Context context) {
          String providerPkg = Settings.Global.getString(context.getContentResolver(),
                  SEARCH_PROVIDER_SETTINGS_KEY);
          if (providerPkg == null) {
              SearchManager searchManager = (SearchManager)context.getSystemService(SEARCH_SERVICE);
              ComponentName componentName = searchManager.getGlobalSearchActivity();
              if (componentName != null) {
                  providerPkg = searchManager.getGlobalSearchActivity().getPackageName();
              }
          }
          return providerPkg;
      }
  
      /**
       * returns it's AppWidgetProviderInfo using package name from getSearchWidgetPackageName
       * @param context
       * @return AppWidgetProviderInfo
       */
      @Nullable
      public static AppWidgetProviderInfo getSearchWidgetProviderInfo(@NonNull Context context) {
          String providerPkg = getSearchWidgetPackageName(context);
          if (providerPkg == null) {
              return null;
          }
  
          AppWidgetProviderInfo defaultWidgetForSearchPackage = null;
          AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
          List<AppWidgetProviderInfo> list = Utilities.ATLEAST_OREO
                  ? appWidgetManager.getInstalledProvidersForPackage(providerPkg, null)
                  : appWidgetManager.getInstalledProviders();
          for (AppWidgetProviderInfo info : list) {
              if (info.provider.getPackageName().equals(providerPkg) && info.configure == null) {
                  if ((info.widgetCategory
                          & AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX) != 0) {
                      return info;
                  } else if (defaultWidgetForSearchPackage == null) {
                      defaultWidgetForSearchPackage = info;
                  }
              }
          }
          return defaultWidgetForSearchPackage;
      }
  
      /**
       * returns componentName for searchWidget if package name is known.
       */
      @Nullable
      public static ComponentName getSearchComponentName(@NonNull  Context context) {
          AppWidgetProviderInfo providerInfo =
                  QsbContainerView.getSearchWidgetProviderInfo(context);
          if (providerInfo != null) {
              return providerInfo.provider;
          } else {
              String pkgName = QsbContainerView.getSearchWidgetPackageName(context);
              if (pkgName != null) {
                  //we don't know the class name yet. we'll put the package name as placeholder
                  return new ComponentName(pkgName, pkgName);
              }
              return null;
          }
      }
  
      public QsbContainerView(Context context) {
          super(context);
      }
  
      public QsbContainerView(Context context, AttributeSet attrs) {
          super(context, attrs);
      }
  
      public QsbContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
          super(context, attrs, defStyleAttr);
      }
  
      @Override
      public void setPadding(int left, int top, int right, int bottom) {
          super.setPadding(0, 0, 0, 0);
      }
  
      protected void setPaddingUnchecked(int left, int top, int right, int bottom) {
          super.setPadding(left, top, right, bottom);
      }
  
      /**
       * A fragment to display the QSB.
       */
      public static class QsbFragment extends FragmentWithPreview {
  
          public static final int QSB_WIDGET_HOST_ID = 1026;
          private static final int REQUEST_BIND_QSB = 1;
  
          protected String mKeyWidgetId = "qsb_widget_id";
          private QsbWidgetHost mQsbWidgetHost;
          private AppWidgetProviderInfo mWidgetInfo;
          private QsbWidgetHostView mQsb;
  
          // We need to store the orientation here, due to a bug (b/64916689) that results in widgets
          // being inflated in the wrong orientation.
          private int mOrientation;
  
          @Override
          public void onInit(Bundle savedInstanceState) {
              mQsbWidgetHost = createHost();
              mOrientation = getContext().getResources().getConfiguration().orientation;
          }
  
          protected QsbWidgetHost createHost() {
              return new QsbWidgetHost(getContext(), QSB_WIDGET_HOST_ID,
                      (c) -> new QsbWidgetHostView(c), this::rebindFragment);
          }
  
          private FrameLayout mWrapper;
  
          @Override
          public View onCreateView(
                  LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
  
              mWrapper = new FrameLayout(getContext());
              // Only add the view when enabled
              if (isQsbEnabled()) {
                  mQsbWidgetHost.startListening();
                  mWrapper.addView(createQsb(mWrapper));
              }
              return mWrapper;
          }
  
          private View createQsb(ViewGroup container) {
              mWidgetInfo = getSearchWidgetProvider();
              if (mWidgetInfo == null) {
                  // There is no search provider, just show the default widget.
                  return getDefaultView(container, false /* show setup icon */);
              }
              Bundle opts = createBindOptions();
              Context context = getContext();
              AppWidgetManager widgetManager = AppWidgetManager.getInstance(context);
  
              int widgetId = Utilities.getPrefs(context).getInt(mKeyWidgetId, -1);
              AppWidgetProviderInfo widgetInfo = widgetManager.getAppWidgetInfo(widgetId);
              boolean isWidgetBound = (widgetInfo != null) &&
                      widgetInfo.provider.equals(mWidgetInfo.provider);
  
              int oldWidgetId = widgetId;
              if (!isWidgetBound && !isInPreviewMode()) {
                  if (widgetId > -1) {
                      // widgetId is already bound and its not the correct provider. reset host.
                      mQsbWidgetHost.deleteHost();
                  }
  
                  widgetId = mQsbWidgetHost.allocateAppWidgetId();
                  isWidgetBound = widgetManager.bindAppWidgetIdIfAllowed(
                          widgetId, mWidgetInfo.getProfile(), mWidgetInfo.provider, opts);
                  if (!isWidgetBound) {
                      mQsbWidgetHost.deleteAppWidgetId(widgetId);
                      widgetId = -1;
                  }
  
                  if (oldWidgetId != widgetId) {
                      saveWidgetId(widgetId);
                  }
              }
  
              if (isWidgetBound) {
                  mQsb = (QsbWidgetHostView) mQsbWidgetHost.createView(context, widgetId,
                          mWidgetInfo);
                  mQsb.setId(R.id.qsb_widget);
  
                  if (!isInPreviewMode()) {
                      if (!containsAll(AppWidgetManager.getInstance(context)
                              .getAppWidgetOptions(widgetId), opts)) {
                          mQsb.updateAppWidgetOptions(opts);
                      }
                  }
                  return mQsb;
              }
  
              // Return a default widget with setup icon.
              return getDefaultView(container, true /* show setup icon */);
          }
  
          private void saveWidgetId(int widgetId) {
              Utilities.getPrefs(getContext()).edit().putInt(mKeyWidgetId, widgetId).apply();
          }
  
          @Override
          public void onActivityResult(int requestCode, int resultCode, Intent data) {
              if (requestCode == REQUEST_BIND_QSB) {
                  if (resultCode == Activity.RESULT_OK) {
                      saveWidgetId(data.getIntExtra(EXTRA_APPWIDGET_ID, -1));
                      rebindFragment();
                  } else {
                      mQsbWidgetHost.deleteHost();
                  }
              }
          }
  
          @Override
          public void onResume() {
              super.onResume();
              if (mQsb != null && mQsb.isReinflateRequired(mOrientation)) {
                  rebindFragment();
              }
          }
  
          @Override
          public void onDestroy() {
              try {
                  mQsbWidgetHost.stopListening();
              } catch (Throwable e){}
              super.onDestroy();
          }
  
          protected void rebindFragment() {
              // Exit if the embedded qsb is disabled
              if (!isQsbEnabled()) {
                  return;
              }
  
              if (mWrapper != null && getContext() != null) {
                  mWrapper.removeAllViews();
                  mWrapper.addView(createQsb(mWrapper));
              }
          }
  
          public boolean isQsbEnabled() {
              return FeatureFlags.QSB_ON_FIRST_SCREEN;
          }
  
          protected Bundle createBindOptions() {
              InvariantDeviceProfile idp = LauncherAppState.getIDP(getContext());
  
              Bundle opts = new Bundle();
              Rect size = AppWidgetResizeFrame.getWidgetSizeRanges(getContext(),
                      idp.numColumns, 1, null);
              opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, size.left);
              opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, size.top);
              opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, size.right);
              opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, size.bottom);
              return opts;
          }
  
          protected View getDefaultView(ViewGroup container, boolean showSetupIcon) {
              // Return a default widget with setup icon.
              View v = QsbWidgetHostView.getDefaultView(container);
              if (showSetupIcon) {
                  View setupButton = v.findViewById(R.id.btn_qsb_setup);
                  setupButton.setVisibility(View.VISIBLE);
                  setupButton.setOnClickListener((v2) -> startActivityForResult(
                          new Intent(ACTION_APPWIDGET_BIND)
                                  .putExtra(EXTRA_APPWIDGET_ID, mQsbWidgetHost.allocateAppWidgetId())
                                  .putExtra(EXTRA_APPWIDGET_PROVIDER, mWidgetInfo.provider),
                          REQUEST_BIND_QSB));
                  //setupButton.callOnClick();
              }
              return v;
          }
  
  
          /**
           * Returns a widget with category {@link AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX}
           * provided by the package from getSearchProviderPackageName
           * If widgetCategory is not supported, or no such widget is found, returns the first widget
           * provided by the package.
           */
          protected AppWidgetProviderInfo getSearchWidgetProvider() {
              return getSearchWidgetProviderInfo(getContext());
          }
      }
  
      public static class QsbWidgetHost extends AppWidgetHost {
  
          private final WidgetViewFactory mViewFactory;
          private final WidgetProvidersUpdateCallback mWidgetsUpdateCallback;
  
          public QsbWidgetHost(Context context, int hostId, WidgetViewFactory viewFactory,
                  WidgetProvidersUpdateCallback widgetProvidersUpdateCallback) {
              super(context, hostId);
              mViewFactory = viewFactory;
              mWidgetsUpdateCallback = widgetProvidersUpdateCallback;
          }
  
          public QsbWidgetHost(Context context, int hostId, WidgetViewFactory viewFactory) {
              this(context, hostId, viewFactory, null);
          }
  
          @Override
          protected AppWidgetHostView onCreateView(
                  Context context, int appWidgetId, AppWidgetProviderInfo appWidget) {
              return mViewFactory.newView(context);
          }
  
          @Override
          protected void onProvidersChanged() {
              super.onProvidersChanged();
              if (mWidgetsUpdateCallback != null) {
                  mWidgetsUpdateCallback.onProvidersUpdated();
              }
          }
      }
  
      public interface WidgetViewFactory {
  
          QsbWidgetHostView newView(Context context);
      }
  
      /**
       * Callback interface for packages list update.
       */
      @FunctionalInterface
      public interface WidgetProvidersUpdateCallback {
          /**
           * Gets called when widget providers list changes
           */
          void onProvidersUpdated();
      }
  
      /**
       * Returns true if {@param original} contains all entries defined in {@param updates} and
       * have the same value.
       * The comparison uses {@link Object#equals(Object)} to compare the values.
       */
      private static boolean containsAll(Bundle original, Bundle updates) {
          for (String key : updates.keySet()) {
              Object value1 = updates.get(key);
              Object value2 = original.get(key);
              if (value1 == null) {
                  if (value2 != null) {
                      return false;
                  }
              } else if (!value1.equals(value2)) {
                  return false;
              }
          }
          return true;
      }
  
  }
  