{*******************************************************}
{                                                       }
{       Helpers for Android                             }
{                                                       }
{       Copyright (C) 2020 KngStr                       }
{                                                       }
{   Some Code from                                      }
{     Kastri Free of DelphiWorlds                       }
{     QDAC of swish                                     }
{   Thanks                                              }
{                                                       }
{*******************************************************}

unit ksAndroid.Helpers;

interface

uses
  System.SysUtils,
  Androidapi.JNI.GraphicsContentViewText, Androidapi.JNI.App,
  Androidapi.JNI.JavaTypes, Androidapi.JNI.Net;

type
  TAndroidHelperEx = record
  private
    class function GetJActivity: JActivity; static;
    class function GetJContext: JContext; static;
    class function GetJActivityManager: JActivityManager; static;
  public
    const
      ICE_CREAM_SANDWICH = 14;
      ICE_CREAM_SANDWICH_MR1 = 15;
      JELLY_BEAN = 16;
      JELLY_BEAN_MR1 = 17;
      JELLY_BEAN_MR2 = 18;
      KITKAT = 19;
      KITKAT_MR1 = 20;
      LOLLIPOP = 21;
      LOLLIPOP_MR1 = 22;
      MARSHMALLOW = 23;
      NOUGAT = 24;
      NOUGAT_MR1 = 25;
      OREO = 26;
      OREO_MR1 = 27;
      PIE = 28;
      Q = 29;
    /// <summary>
    ///   Checks if both build and target are greater or equal to the tested value
    /// </summary>
    class function CheckBuildAndTarget(const AValue: Integer): Boolean; static;
    /// <summary>
    ///   Returns the equivalent of "AndroidClass.class"
    /// </summary>
    class function GetClass(const APackageClassName: string): Jlang_Class; static;
    /// <summary>
    ///   Returns the application default icon ID
    /// </summary>
    class function GetDefaultIconID: Integer; static;
    /// <summary>
    ///   Returns a URI to the notification sound
    /// </summary>
    class function GetDefaultNotificationSound: Jnet_Uri; static;
    /// <summary>
    ///   Returns target Sdk version
    /// </summary>
    class function GetTargetSdkVersion: Integer; static;
    /// <summary>
    ///   Returns installed Sdk version
    /// </summary>
    class function GetBuildSdkVersion: Integer; static;
    /// <summary>
    ///   Returns whether the activity is running foreground
    /// </summary>
    /// <remarks>
    ///   Useful from within a service to determine whether or not the service needs to run in foreground mode
    /// </remarks>
    class function IsActivityForeground: Boolean; static;
    /// <summary>
    ///   Converts file to uri, using FileProvider if target API >= 24
    /// </summary>
    /// <remarks>
    ///   Use this only when accessing files with an "external" URI
    /// </remarks>
    class function UriFromFile(const AFile: JFile; const AAuthority: string = ''): Jnet_Uri; overload; static;
    /// <summary>
    ///   Converts filename to uri, using FileProvider if target API >= 24
    /// </summary>
    /// <remarks>
    ///   Use this only when accessing files with an "external" URI
    /// </remarks>
    class function UriFromFile(const AFileName: string; const AAuthority: string = ''): Jnet_Uri; overload; static;

    /// <summary>Returns Java Application Context</summary>
    class property Context: JContext read GetJContext;
    /// <summary>Returns Java Application Activity</summary>
    /// <remarks>An exception will be launched if there is no activity, for example a Service</remarks>
    class property Activity: JActivity read GetJActivity;
    /// <summary>Returns Java Application Activity Manager</summary>
    class property ActivityManager: JActivityManager read GetJActivityManager;

    /// <remarks>Need reorder tasks permission</remarks>
    class procedure BringAppToFront; static;
    /// <remarks>Need reorder tasks permission</remarks>
    class procedure SendAppToBack; static;
    /// <summary>Call a Java Activity</summary>
    class function StartActivity(Intent: JIntent; const Code: Integer = -1): Boolean; static;

    /// <summary>Returns Application package name</summary>
    class function GetPackageName: string; static;
    /// <summary>Returns MimeType from filename</summary>
    class function GetMimeType(AFileName: string): JString; static;

    /// <summary>Checks if there is at least one application capable of receiving the intent.</summary>
    class function HasAssocApp(const URI: string): Boolean; overload; static;
    /// <summary>Checks if there is at least one application capable of receiving the intent.</summary>
    class function HasAssocApp(const Intent: JIntent): Boolean; overload; static;

    /// <summary>Install an android package: xxx.apk</summary>
    /// <remarks>
    /// <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
    /// </remarks>
    class function InstallPackage(const AFileName: string; const AAuthority: string = ''): Boolean; static;
    /// <summary>Check if an application is installed</summary>
    class function IsAppInstalled(const APackage: string): Boolean; static;

    /// <summary>Add/Cear FLAG_KEEP_SCREEN_ON</summary>
    class function KeepScreen(AOn: Boolean): Boolean; static;
  end;

implementation

uses
  Androidapi.JNIBridge, Androidapi.JNI.Os, Androidapi.JNI.Support,
  Androidapi.JNI.Media, Androidapi.JNI.Provider, Androidapi.JNI.Webkit,
  FMX.Helpers.Android, Androidapi.Helpers;

{ TAndroidHelperEx }

class procedure TAndroidHelperEx.BringAppToFront;
begin
  ActivityManager.moveTaskToFront(Activity.getTaskId, TJIntent.JavaClass.FLAG_ACTIVITY_NEW_TASK);
end;

class function TAndroidHelperEx.CheckBuildAndTarget(const AValue: Integer): Boolean;
begin
  Result := (GetBuildSdkVersion >= AValue) and (GetTargetSdkVersion >= AValue);
end;

class function TAndroidHelperEx.GetBuildSdkVersion: Integer;
begin
   Result := TJBuild_VERSION.JavaClass.SDK_INT;
end;

class function TAndroidHelperEx.GetClass(const APackageClassName: string): Jlang_Class;
begin
  Result := TJLang_Class.JavaClass.forName(StringToJString(APackageClassName), True, Context.getClassLoader);
end;

class function TAndroidHelperEx.GetDefaultIconID: Integer;
begin
  Result := Context.getApplicationInfo.icon;
end;

class function TAndroidHelperEx.GetDefaultNotificationSound: Jnet_Uri;
begin
  Result := TJRingtoneManager.JavaClass.getDefaultUri(TJRingtoneManager.JavaClass.TYPE_NOTIFICATION);
end;

class function TAndroidHelperEx.GetJActivity: JActivity;
begin
  Result :=
{$IF CompilerVersion > 27}
  TAndroidHelper.Activity
{$ELSE}
  SharedActivity
{$ENDIF}
end;

class function TAndroidHelperEx.GetJActivityManager: JActivityManager;
var
  AService: JObject;
begin
  AService := Context.getSystemService(TJContext.JavaClass.ACTIVITY_SERVICE);
  Result := TJActivityManager.Wrap((AService as ILocalObject).GetObjectID);
end;

class function TAndroidHelperEx.GetJContext: JContext;
begin
  Result :=
{$IF CompilerVersion > 27}
  TAndroidHelper.Context;
{$ELSE}
  SharedActivityContext;
{$ENDIF}
end;

class function TAndroidHelperEx.GetMimeType(AFileName: string): JString;
var
  LExt: string;
  LTypeMap: JMimeTypeMap;
begin
  Result := nil;
  if AFileName = '' then
    Exit;
  LExt := LowerCase(ExtractFileExt(AFileName));
  if Length(LExt) < 2 then
    Exit;
  LExt := Copy(LExt, 1);
  LTypeMap := TJMimeTypeMap.JavaClass.getSingleton();
  if LTypeMap = nil then
    Exit;
  Result := LTypeMap.getMimeTypeFromExtension(StringToJString(LExt));
  if Result <> nil then
    Result := Result.trim();
end;

class function TAndroidHelperEx.GetPackageName: string;
begin
  Result := JStringToString(Context.getPackageName);
end;

class function TAndroidHelperEx.GetTargetSdkVersion: Integer;
var
  LApplicationInfo: JApplicationInfo;
begin
  LApplicationInfo := Context.getPackageManager.getApplicationInfo(Context.getPackageName, 0);
  Result := LApplicationInfo.targetSdkVersion;
end;

class function TAndroidHelperEx.HasAssocApp(const Intent: JIntent): Boolean;
var
  LList: JList;
begin
  // Android 6+ APP LINK closed Will cause list is null.
  // Activity with <action android:name="android.intent.action.VIEW" />
  LList := Activity.getPackageManager.queryIntentActivities(Intent,
    TJPackageManager.JavaClass.MATCH_DEFAULT_ONLY);
  Result := (LList <> nil) and (LList.size > 0);
end;

class function TAndroidHelperEx.HasAssocApp(const URI: string): Boolean;
var
  Intent: JIntent;
begin
  Result := False;
  Intent := TJIntent.Create;
  Intent.setData(TJnet_Uri.JavaClass.parse(StringToJString(URI)));
  Intent.setAction(StringToJString('android.intent.action.VIEW'));
  Result := HasAssocApp(Intent);
end;

class function TAndroidHelperEx.InstallPackage(const AFileName, AAuthority: string): Boolean;
var
  LIntent: JIntent;
begin
  Result := False;
  if Trim(AFileName) = '' then
    Exit;
  LIntent := TJIntent.Create;
  if CheckBuildAndTarget(OREO) then
    LIntent.setAction(TJIntent.JavaClass.ACTION_INSTALL_PACKAGE)
  else
    LIntent.setAction(TJIntent.JavaClass.ACTION_VIEW);
  // û�����Ҳ���԰�װ�ɹ������ǰ�װ�ɹ���ijɹ�ҳ�棬Ҳ���ǣ����/�� ���޷���ʾ
  LIntent.addFlags(TJIntent.JavaClass.FLAG_ACTIVITY_NEW_TASK);
  LIntent.setDataAndType(UriFromFile(AFileName, AAuthority),
    StringToJString('application/vnd.android.package-archive'));
  LIntent.addFlags(TJIntent.JavaClass.FLAG_GRANT_READ_URI_PERMISSION);
  Result := StartActivity(LIntent);
end;

class function TAndroidHelperEx.IsActivityForeground: Boolean;
var
  LService: JObject;
  LRunningApps: JList;
  LAppInfo: JActivityManager_RunningAppProcessInfo;
  I: Integer;
begin
  Result := False;
  LService := Context.getSystemService(TJContext.JavaClass.ACTIVITY_SERVICE);
  LRunningApps := TJActivityManager.Wrap(TAndroidHelper.JObjectToID(LService)).getRunningAppProcesses;
  for I := 0 to LRunningApps.size - 1 do begin
    LAppInfo := TJActivityManager_RunningAppProcessInfo.Wrap(TAndroidHelper.JObjectToID(LRunningApps.get(I)));
    if LAppInfo.importance = 100 then begin
      if LAppInfo.importanceReasonComponent <> nil then begin
        if LAppInfo.importanceReasonComponent.getPackageName.equals(Context.getPackageName) then
          Exit(True);
      end
      else if LRunningApps.size = 1 then
        Exit(True);
    end;
  end;
end;

class function TAndroidHelperEx.IsAppInstalled(const APackage: string): Boolean;
begin
  Result := False;
  try
    //ֻ���쳣�ǿɿ��ģ�����ֵ�ж�����
    Result := Context.getPackageManager.getPackageInfo(StringToJString(APackage),
      TJPackageManager.JavaClass.GET_ACTIVITIES) = nil;
    Result := True;
  except
  end;
end;

class function TAndroidHelperEx.KeepScreen(AOn: Boolean): Boolean;
begin
  CallInUIThreadAndWaitFinishing(
    procedure begin
      if AOn then
        SharedActivity.getWindow.addFlags(
          TJWindowManager_LayoutParams.JavaClass.FLAG_KEEP_SCREEN_ON)
      else
        SharedActivity.getWindow.clearFlags(
          TJWindowManager_LayoutParams.JavaClass.FLAG_KEEP_SCREEN_ON);
    end);
end;

class procedure TAndroidHelperEx.SendAppToBack;
begin
  //SharedActivityManager.moveTaskToBack(Activity.getTaskId, TJIntent.JavaClass.FLAG_ACTIVITY_NEW_TASK);
  Activity.moveTaskToBack(True);
end;

class function TAndroidHelperEx.StartActivity(Intent: JIntent; const Code: Integer): Boolean;
begin
  Result := False;
  if HasAssocApp(Intent) then begin
    if Code = -1 then
      Activity.startActivity(Intent)
    else
      Activity.startActivityForResult(Intent, Code);
    Result := True;
  end;
end;

class function TAndroidHelperEx.UriFromFile(const AFile: JFile; const AAuthority: string): Jnet_Uri;
var
  LAuthority: JString;
begin
  if CheckBuildAndTarget(NOUGAT) then begin
    if AAuthority <> '' then
      LAuthority := StringToJString(AAuthority)
    else
      LAuthority := Context.getApplicationContext.getPackageName.concat(StringToJString('.fileprovider'));
    Result := TJFileProvider.JavaClass.getUriForFile(Context, LAuthority, AFile);
  end
  else
    Result := TJnet_uri.JavaClass.fromFile(AFile);
end;

class function TAndroidHelperEx.UriFromFile(const AFileName, AAuthority: string): Jnet_Uri;
begin
  Result := UriFromFile(TJFile.JavaClass.init(StringToJString(AFileName)), AAuthority);
end;

end.