Feature Request: New Task Checkbox for Activity Start in MIT App Inventor

Feature Request: I would like to propose the addition of a checkbox option in the Activity Start component that allows developers to specify whether the started activity should open in a new app using the Intent Flag Intent.FLAG_ACTIVITY_NEW_TASK or remain within the current app context.

Why this feature is important:

  1. Enhanced Multi-App Integration: This feature would be particularly beneficial for applications that require seamless integration with other apps or services. By allowing an activity to start in a new task, it becomes easier to pass data and control flow between different apps, enhancing the overall user experience.
  2. Improved App Modularity: The checkbox would also cater to developers who wish to keep their apps modular and lightweight. Opening certain activities in a new app task would enable them to distribute functionalities across multiple apps, resulting in cleaner and more manageable codebases.
  3. Increased Flexibility: The flexibility to choose whether an activity should open in a new task or not would cater to a broader range of use cases. Developers could adapt their apps to suit specific user needs and scenarios.

Implementation Suggestion:

  • Add a checkbox option in the Activity Start component that allows users to select whether the activity should open in a new task.
  • If the checkbox is selected, the component could internally set the Intent.FLAG_ACTIVITY_NEW_TASK flag to achieve this behavior.
  • If the checkbox is not selected, the activity would behave as it does currently, staying within the current app context.

I believe that this feature would greatly expand the capabilities of MIT App Inventor, making it even more versatile and user-friendly for developers. It would open up new possibilities for creating innovative Android applications that seamlessly interact with other apps while providing greater control over the app's structure.

Dear @job_bot, your feature request does not make any sense, because using the activity starter to start another app always starts a new app, so there really is no use to have a checkbox. Btw. greetings to ChatGPT... And if you like to stay in the same app, just use the open another screen block to open another screen of the same app.... hope that helps...

Taifun

2 Likes

I doubt that as new task flag is not added to intent.

I looked at source code on github, it is not.

Yes, but it works fine, maybe because of using startActivityForResult() in every condition so control is always returned to your own app.
You should provide a demo aia if it doesn't work in the way you want.

I downloaded the source for the activty starter and made an rush extention to do it. Here is the code, the result event does not work because rush would not compile the extention if it was present feel free to close this thread. I apologize but I had to make it quickly for a project. so it is a bit rough around the edges. I apologize for that.

package com.test.StarterV2;

import com.google.appinventor.components.annotations.SimpleFunction;
import com.google.appinventor.components.runtime.AndroidNonvisibleComponent;
import com.google.appinventor.components.runtime.ComponentContainer;
import com.google.appinventor.components.runtime.errors.YailRuntimeError;
import com.google.appinventor.components.runtime.util.YailList;
import android.app.Activity;
import android.app.Activity;
import android.content.Context;
import com.google.appinventor.components.annotations.*;
import com.google.appinventor.components.common.ComponentCategory;
import com.google.appinventor.components.runtime.AndroidNonvisibleComponent;
import com.google.appinventor.components.runtime.ComponentContainer;
import com.google.appinventor.components.runtime.EventDispatcher;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;

import android.net.Uri;

import android.text.TextUtils;

import android.util.Log;


import com.google.appinventor.components.annotations.DesignerProperty;
import com.google.appinventor.components.annotations.PropertyCategory;
import com.google.appinventor.components.annotations.SimpleEvent;
import com.google.appinventor.components.annotations.SimpleFunction;
import com.google.appinventor.components.annotations.SimpleProperty;

import com.google.appinventor.components.common.ComponentCategory;
import com.google.appinventor.components.common.PropertyTypeConstants;

import com.google.appinventor.components.runtime.errors.YailRuntimeError;

import com.google.appinventor.components.runtime.util.AnimationUtil;
import com.google.appinventor.components.runtime.util.ErrorMessages;
import com.google.appinventor.components.runtime.util.NougatUtil;
import com.google.appinventor.components.runtime.util.YailList;

import java.io.File;
public class StarterV2 extends AndroidNonvisibleComponent{
  private String action;
  private String dataUri;
  private String dataType;
  private String activityPackage;
  private String activityClass;
  private String extraKey;
  private String extraValue;
  private String resultName;
  private Intent resultIntent;
  private String result;
  private int requestCode;
  private YailList extras;
  private final ComponentContainer container;

  private static final String LOG_TAG = "ActivityStarter";
  public StarterV2(ComponentContainer container) {
    super(container.$form());
    // Save the container for later
    this.container = container;
    result = "";
    Action(Intent.ACTION_MAIN);
    ActivityPackage("");
    ActivityClass("");
    DataUri("");
    DataType("");
    ExtraKey("");
    ExtraValue("");
    Extras(new YailList());
    ResultName("");
  }
 /**
   * Returns the action that will be used to start the activity.
   */
  @SimpleProperty(
      )
  public String Action() {
    return action;
  }

  /**
   * Specifies the action that will be used to start the activity.
   */
  @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING,
      defaultValue = "")
  @SimpleProperty
  public void Action(String action) {
    this.action = action.trim();
  }

  // TODO(lizlooney) - currently we support just one extra name/value pair that will be passed to
  // the activity. The user specifies the ExtraKey and ExtraValue properties.
  // We should allow more extra name/value pairs, but we'd need a different interface with regard
  // to properties and functions.
  // In the documentation for Intent, they use the term "name", not "key", and we might want to use
  // the term "name", also.
  // There are backwards compatibility issues with removing the ExtraKey and ExtraValue properties.
  // Also, while extra names are always Strings, the values can be other types. We'd need to know
  // the correct type of the value in order to call the appropriate Intent.putExtra method.
  // Adding multiple functions like PutStringExtra, PutStringArrayExtra, PutCharExtra,
  // PutCharArrayExtra, PutBooleanExtra, PutBooleanArrayExtra, PutByteExtra, PutByteArrayExtra,
  // PutShortExtra, PutShortArrayExtra, PutIntExtra, PutIntArrayExtra, PutLongExtra,
  // PutLongArrayExtra, PutFloatExtra, PutFloatArrayExtra, PutDoubleExtra, PutDoubleArrayExtra,
  // etc, seems like a bad idea.

  /**
   * Returns the extra key that will be passed to the activity.
   * Obsolete. Should use Extras instead
   */
  @SimpleProperty()
  public String ExtraKey() {
    return extraKey;
  }

  /**
   * Specifies the extra key that will be passed to the activity.
   * Obsolete. Should use Extras instead
   */
  @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING,
      defaultValue = "")
  @SimpleProperty
  public void ExtraKey(String extraKey) {
    this.extraKey = extraKey.trim();
  }


  /**
   * Returns the extra value that will be passed to the activity.
   * Obsolete. Should use Extras instead
   */
  @SimpleProperty()
  public String ExtraValue() {
    return extraValue;
  }

  /**
   * Specifies the extra value that will be passed to the activity.
   * Obsolete. Should use Extras instead
   */
  @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING,
      defaultValue = "")
  @SimpleProperty
  public void ExtraValue(String extraValue) {
    this.extraValue = extraValue.trim();
  }

  // TODO(lizlooney) - currently we support retrieving just one string extra result from the
  // activity. The user specifies the ResultName property and, then after the activity finishes,
  // the string extra result corresponding to ResultName is passed as the result parameter to the
  // AfterActivity event and is also available from the Result property getter.
  // We should allow access to more extra results, but we'd need a different interface with regard
  // to properties, functions, and events parameters.
  // There are backwards compatibility issues with removing the AfterActivity event's result
  // parameter and the Result property.
  // Also, while extra names are always Strings, the values can be other types. We'd need to know
  // the correct type of the value in order to call the appropriate Intent.get...Extra method.
  // Adding multiple functions like GetStringExtra, GetStringArrayExtra, GetCharExtra,
  // GetCharArrayExtra, GetBooleanExtra, GetBooleanArrayExtra, GetByteExtra, GetByteArrayExtra,
  // GetShortExtra, GetShortArrayExtra, GetIntExtra, GetIntArrayExtra, GetLongExtra,
  // GetLongArrayExtra, GetFloatExtra, GetFloatArrayExtra, GetDoubleExtra, GetDoubleArrayExtra,
  // etc, seems like a bad idea.

  /**
   * Returns the name that will be used to retrieve a result from the activity.
   */
  @SimpleProperty(
      )
  public String ResultName() {
    return resultName;
  }

  /**
   * Specifies the name that will be used to retrieve a result from the
   * activity.
   */
  @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING,
      defaultValue = "")
  @SimpleProperty
  public void ResultName(String resultName) {
    this.resultName = resultName.trim();
  }

  /**
   * Returns the result from the activity.
   */
  @SimpleProperty(
      )
  public String Result() {
    return result;
  }

  /**
   * Returns the data URI that will be used to start the activity.
   */
  @SimpleProperty(
      )
  public String DataUri() {
    return dataUri;
  }

  /**
   * Specifies the data URI that will be used to start the activity.
   */
  @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING,
      defaultValue = "")
  @SimpleProperty
  public void DataUri(String dataUri) {
    this.dataUri = dataUri.trim();
  }

  /**
   * Returns the MIME type to pass to the activity.
   */
  @SimpleProperty(
      )
  public String DataType() {
    return dataType;
  }

  /**
   * Specifies the MIME type to pass to the activity.
   */
  @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING,
      defaultValue = "")
  @SimpleProperty
  public void DataType(String dataType) {
    this.dataType = dataType.trim();
  }

  /**
   * Returns the package part of the specific component that will be started.
   */
  @SimpleProperty(
      )
  public String ActivityPackage() {
    return activityPackage;
  }

  /**
   * Specifies the package part of the specific component that will be started.
   */
  @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING,
      defaultValue = "")
  @SimpleProperty
  public void ActivityPackage(String activityPackage) {
    this.activityPackage = activityPackage.trim();
  }

  /**
   * Returns the class part of the specific component that will be started.
   */
  @SimpleProperty(
      )
  public String ActivityClass() {
    return activityClass;
  }

  /**
   * Specifies the class part of the specific component that will be started.
   */
  @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING,
      defaultValue = "")
  @SimpleProperty
  public void ActivityClass(String activityClass) {
    this.activityClass = activityClass.trim();
  }

  /**
   * Event raised after this `ActivityStarter` returns.
   * @param result The result returned by the activity
   */
  @SimpleEvent(description = "Event raised after this ActivityStarter returns.")
  public void AfterActivity(String result) {
    EventDispatcher.dispatchEvent(this, "AfterActivity", result);
  }

  /**
   * Event raised if this `ActivityStarter returns because the activity was canceled.
   */
  @SimpleEvent(description =
      "Event raised if this ActivityStarter returns because the activity was canceled.")
  public void ActivityCanceled() {
    EventDispatcher.dispatchEvent(this, "ActivityCanceled");
  }

  /**
   * Returns the MIME type from the activity.
   */
  @SimpleProperty(
      )
  public String ResultType() {
    if (resultIntent != null) {
      String resultType = resultIntent.getType();
      if (resultType != null) {
        return resultType;
      }
    }
    return "";
  }

  /**
   * Returns the URI from the activity.
   */
  @SimpleProperty(
      )
  public String ResultUri() {
    if (resultIntent != null) {
      String resultUri = resultIntent.getDataString();
      if (resultUri != null) {
        return resultUri;
      }
    }
    return "";
  }

  /**
   * Specifies the list of key-value pairs that will be passed as extra data to the activity.
   */
  @SimpleProperty
  public void Extras(YailList pairs) {
    for (Object pair : pairs.toArray()) {
      boolean isYailList = pair instanceof YailList;
      boolean isPair = isYailList ? ((YailList) pair).size() == 2 : false;
      if (!isYailList || !isPair) {
        throw new YailRuntimeError("Argument to Extras should be a list of pairs",
            "ActivityStarter Error");
      }
    }
    extras = pairs;
  }

  /**
   * Returns the list of key-value pairs that will be passed as extra data to the activity.
   */
  @SimpleProperty
  public YailList Extras() {
    return extras;
  }


  /**
   * Returns the name of the activity that corresponds to this `ActivityStarter`,
   * or an empty string if no corresponding activity can be found.
   */
  @SimpleFunction(description = "Returns the name of the activity that corresponds to this " +
      "ActivityStarter, or an empty string if no corresponding activity can be found.")
  public String ResolveActivity() {
    Intent intent = buildActivityIntent();
    PackageManager pm = container.$context().getPackageManager();
    ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
    if (resolveInfo != null && resolveInfo.activityInfo != null) {
      return resolveInfo.activityInfo.name;
    }
    return "";
  }

  /**
   * Start the activity corresponding to this `ActivityStarter`.
   */
  @SimpleFunction(description = "Start the activity corresponding to this ActivityStarter.")
  public void StartActivity() {
    resultIntent = null;
    result = "";

    Intent intent = buildActivityIntent();

    if (requestCode == 0) {
      // First time, we need to register this as an ActivityResultListener with the Form.
      // The Form's onActivityResult method will be called when the activity returns. If we
      // register with the Form and then use the requestCode when we start an activity, the Form
      // will call our resultReturned method.
      
    }

    if (intent == null) {
      form.dispatchErrorOccurredEvent(this, "StartActivity",
        ErrorMessages.ERROR_ACTIVITY_STARTER_NO_ACTION_INFO);
    } else {
      try {
        container.$context().startActivityForResult(intent, requestCode);
        String openAnim = container.$form().OpenScreenAnimation();
        AnimationUtil.ApplyOpenScreenAnimation(container.$context(), openAnim);
      } catch (ActivityNotFoundException e) {
        form.dispatchErrorOccurredEvent(this, "StartActivity",
          ErrorMessages.ERROR_ACTIVITY_STARTER_NO_CORRESPONDING_ACTIVITY);
      }
    }
  }

  private Intent buildActivityIntent() {
    Uri uri = (dataUri.length() != 0) ? Uri.parse(dataUri) : null;
    Intent intent = new Intent(action);
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    if (uri != null && dataUri.toLowerCase().startsWith("file://") ) {
      Log.d(LOG_TAG, "Using file://");
      File file = new File(uri.getPath());
      if (file.isFile()) {
        Log.d(LOG_TAG, "It's a file");
        uri = NougatUtil.getPackageUri(form, file);
        intent = new Intent(action);
        intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        
        Log.d(LOG_TAG, "added permissions"); // adb log shows this gets printed
      }
    }

    if (TextUtils.isEmpty(Action())) {
      return null;
    }

    if (dataType.length() != 0) {
      if (uri != null) {
        intent.setDataAndType(uri, dataType);
      } else {
        intent.setType(dataType);
      }
    } else {
      intent.setData(uri);
    }

    if (activityPackage.length() != 0 || activityClass.length() != 0) {
      ComponentName component = new ComponentName(activityPackage, activityClass);
      intent.setComponent(component);
    } else if (Action().equals("android.intent.action.MAIN")) {
      return null;
    }

    if (extraKey.length() != 0 && extraValue.length() != 0) {
      Log.i(LOG_TAG, "Adding extra, key = " + extraKey + " value = " + extraValue);
      intent.putExtra(extraKey, extraValue);
    }

    // If the extra value is a string, put it to the intent. If the extra value is a list
    // of strings, convert it to a java list and put that to the intent.
    for (Object extra : extras.toArray()) {
      YailList castExtra = (YailList) extra;
      String key = castExtra.getString(0);
      Object value = castExtra.getObject(1);
      Log.i(LOG_TAG, "Adding extra, key = " + key + " value = " + value);
      if ((key.length() != 0)) {
        if (value instanceof YailList) {
          Log.i(LOG_TAG, "Adding extra list, key = " + key + " value = " + value);
          intent.putExtra(key, ((YailList) value).toStringArray());
        }
        else {
          String stringValue = castExtra.getString(1);
          Log.i(LOG_TAG, "Adding extra string, key = " + key + " value = " + stringValue);
          intent.putExtra(key, stringValue);
        }
      };
    };
    return intent;
  }



  @SimpleEvent(description = "The ActivityError event is no longer used. " +
      "Please use the Screen.ErrorOccurred event instead."
      )
  public void ActivityError(String message) {
  }

  // Deleteable implementation


  
}