浏览代码

add files

KngStr 5 年之前
当前提交
12b5b863c0
共有 9 个文件被更改,包括 7294 次插入0 次删除
  1. 121 0
      ActiveNetworkInfo.Android.pas
  2. 77 0
      AppIconLoader.pas
  3. 869 0
      FMX.ImageSlider.pas
  4. 3600 0
      FlyFilesUtils.pas
  5. 665 0
      ONE.Objects.pas
  6. 290 0
      UMeng.pas
  7. 1605 0
      ksBitmapHelper.pas
  8. 38 0
      ksFmxUtils.pas
  9. 29 0
      ksObjUtils.pas

+ 121 - 0
ActiveNetworkInfo.Android.pas

@@ -0,0 +1,121 @@
+{ *********************************************************************
+  *
+  * Autor: Efimov A.A.
+  * E-mail: infocean@gmail.com
+  * GitHub: https://github.com/AndrewEfimov
+  * Description: Getting information about connecting.
+  * Requirements: You need permission "ACCESS_NETWORK_STATE" in the manifest.
+  * Platform: only Android (tested on API16+)
+  * IDE: Delphi 10.1 Berlin +
+  *
+  ******************************************************************** }
+unit ActiveNetworkInfo.Android;
+
+interface
+
+uses
+  Androidapi.JNI.Net, Androidapi.Helpers, Androidapi.JNI.GraphicsContentViewText, Androidapi.JNI.JavaTypes,
+  Androidapi.JNI.Os;
+
+type
+  TActiveNetworkInfo = class
+  private
+    class var FJConnectivityManager: JConnectivityManager;
+    class constructor Create;
+  public
+    /// <summary>Check permission "android.permission.ACCESS_NETWORK_STATE"</summary>
+    class function CheckPermission: Boolean;
+    /// <summary>Returns details about the currently active default data network.</summary>
+    class function GetInfo: JNetworkInfo;
+    /// <summary>Indicates whether network connectivity exists and it is possible to establish connections and pass data.</summary>
+    class function IsConnected: Boolean;
+    /// <summary>Return a human-readable name describe the type of the network, for example "WIFI" or "MOBILE".</summary>
+    class function GetTypeName: string;
+    /// <summary>Is Wi-Fi connection?</summary>
+    class function IsWifi: Boolean;
+    /// <summary>Is Mobile connection?</summary>
+    class function IsMobile: Boolean;
+  end;
+
+implementation
+
+{ TActiveNetworkInfo }
+
+class function TActiveNetworkInfo.CheckPermission: Boolean;
+const
+  AccessNetworkStatePermission = 'android.permission.ACCESS_NETWORK_STATE';
+var
+  PackageName, Permission: JString;
+  Context: JContext;
+  PermissionGranted: Integer;
+begin
+  PackageName := TAndroidHelper.Context.getPackageName;
+  Context := TAndroidHelper.Context;
+  Permission := StringToJString(AccessNetworkStatePermission);
+  PermissionGranted := TJPackageManager.JavaClass.PERMISSION_GRANTED;
+
+  if TJBuild_VERSION.JavaClass.SDK_INT < 23 then
+    Result := Context.getPackageManager.CheckPermission(Permission, PackageName) = PermissionGranted
+  else
+    Result := Context.checkSelfPermission(Permission) = PermissionGranted;
+end;
+
+class constructor TActiveNetworkInfo.Create;
+begin
+  FJConnectivityManager := nil;
+  if CheckPermission then
+    FJConnectivityManager := TJConnectivityManager.Wrap
+      (TAndroidHelper.Context.getSystemService(TJContext.JavaClass.CONNECTIVITY_SERVICE));
+end;
+
+class function TActiveNetworkInfo.GetInfo: JNetworkInfo;
+var
+  NetworkInfo: JNetworkInfo;
+begin
+  Result := nil;
+  if FJConnectivityManager <> nil then
+  begin
+    NetworkInfo := FJConnectivityManager.getActiveNetworkInfo();
+    if NetworkInfo <> nil then
+      Result := NetworkInfo;
+  end;
+end;
+
+class function TActiveNetworkInfo.IsConnected: Boolean;
+var
+  NetworkInfo: JNetworkInfo;
+begin
+  NetworkInfo := GetInfo;
+  Result := (NetworkInfo <> nil) and NetworkInfo.IsConnected();
+end;
+
+class function TActiveNetworkInfo.IsMobile: Boolean;
+var
+  NetworkInfo: JNetworkInfo;
+begin
+  NetworkInfo := GetInfo;
+  Result := (NetworkInfo <> nil) and (NetworkInfo.getType() = TJConnectivityManager.JavaClass.TYPE_MOBILE);
+end;
+
+class function TActiveNetworkInfo.IsWifi: Boolean;
+var
+  NetworkInfo: JNetworkInfo;
+begin
+  NetworkInfo := GetInfo;
+  Result := (NetworkInfo <> nil) and (NetworkInfo.getType() = TJConnectivityManager.JavaClass.TYPE_WIFI);
+end;
+
+class function TActiveNetworkInfo.GetTypeName: string;
+const
+  ResultNone = 'NONE';
+var
+  NetworkInfo: JNetworkInfo;
+begin
+  NetworkInfo := GetInfo;
+  if NetworkInfo <> nil then
+    Result := JStringToString(NetworkInfo.GetTypeName())
+  else
+    Result := ResultNone
+end;
+
+end.

+ 77 - 0
AppIconLoader.pas

@@ -0,0 +1,77 @@
+unit AppIconLoader;
+
+//https://delphihaven.wordpress.com/2014/01/20/loading-the-apps-icon-into-a-timage-on-windows/
+
+interface
+
+uses
+  System.SysUtils, System.Classes, FMX.Graphics;
+
+function GetAppIcon(Dest: TBitmap): Boolean;
+
+implementation
+
+uses
+  {$IF DEFINED(ANDROID)}
+  AndroidApi.JniBridge, AndroidApi.Jni.App,
+  AndroidApi.Jni.GraphicsContentViewText, FMX.Helpers.Android,
+  {$IF CompilerVersion > 27}Androidapi.Helpers, {$ENDIF}
+  {$ELSEIF DEFINED(MSWINDOWS)}
+  Vcl.Graphics,
+  {$ENDIF}
+  FMX.Surfaces;
+
+function GetAppIcon(Dest: FMX.Graphics.TBitmap): Boolean;
+{$IF DEFINED(ANDROID)}
+var
+  Activity: JActivity;
+  Drawable: JDrawable;
+  Bitmap: JBitmap;
+  Surface: TBitmapSurface;
+begin
+  Result := False;
+  {$IF CompilerVersion > 27}
+  Activity := TAndroidHelper.Activity;
+  {$ELSE}
+  Activity := SharedActivity;
+  {$ENDIF}
+  Drawable := Activity.getPackageManager.getApplicationIcon(Activity.getApplicationInfo);
+  Bitmap := TJBitmapDrawable.Wrap((Drawable as ILocalObject).GetObjectID).getBitmap;
+  Surface := TBitmapSurface.Create;
+  try
+    if not JBitmapToSurface(Bitmap, Surface) then
+      Exit;
+    Dest.Assign(Surface);
+  finally
+    Surface.Free;
+  end;
+  Result := True;
+end;
+{$ELSEIF DEFINED(MSWINDOWS)}
+var
+  Icon: TIcon;
+  Stream: TMemoryStream;
+begin
+  Result := False;
+  Stream := nil;
+  Icon := TIcon.Create;
+  try
+    Icon.LoadFromResourceName(HInstance, 'MAINICON');
+    if Icon.Handle = 0 then Exit;
+    Stream := TMemoryStream.Create;
+    Icon.SaveToStream(Stream);
+    Stream.Position := 0;
+    Dest.LoadFromStream(Stream);
+  finally
+    Icon.Free;
+    Stream.Free;
+  end;
+  Result := True;
+end;
+{$ELSE}
+begin
+  Result := False;
+end;
+{$ENDIF}
+
+end.

+ 869 - 0
FMX.ImageSlider.pas

@@ -0,0 +1,869 @@
+// ***************************************************************************
+//
+// FMXComponents: Firemonkey Opensource Components Set from China
+//
+// A Simple Firemonkey Image Slider Component
+//
+// Copyright 2017 谢顿 (zhaoyipeng@hotmail.com)
+//
+// https://github.com/zhaoyipeng/FMXComponents
+//
+// ***************************************************************************
+// version history
+// 2017-01-20, v0.1.0.0 : first release
+// 2018-01-31, v0.2.0.0 : merged with loko's change
+// 2018-03-21, v0.3.0.0 : merged with kwon hwang-jung's change 2018-03-02
+//         1. add three Add methods to add bitmap directly
+//         2. add Next, Prev methods
+//         3. add AutoSlider property can auto slide
+//         4. add TimerInterval to control AutoSlider interval
+//         5. use ActivePage property move page, ex)ActivePage := 1
+//         6. add Datas property, can set tagstring on each page
+// 2018-03-21, v0.4.0.0 : merged with Mikkao's change 2018-03-01
+//         1. change OnPageChange event to TPageChangeEvent
+//         2. add OnPageAnimationFinish event
+//         3. add OnCanDragBegin event
+// 2018-03-21, v0.5.0.0 :
+//         1. rewrite slide method, now can support loop
+// 2018-03-22, v0.6.0.0 :
+//         1. add dot indicator, support dynamic change dot active/inactive color
+
+unit FMX.ImageSlider;
+
+interface
+
+{$I FMXComponents.inc}
+
+uses
+  System.Classes,
+  System.Generics.Collections,
+  System.Types,
+  System.UITypes,
+  System.SysUtils,
+  FMX.Types,
+  FMX.Controls,
+  FMX.Layouts,
+  FMX.Objects,
+  FMX.Ani,
+  FMX.Utils,
+  FMX.Graphics,
+  FMX.ComponentsCommon,
+  UI.Base, UI.Standard;
+
+type
+  TPageChangeEvent = procedure(Sender: TObject; NewPage, OldPage: Integer) of object;
+  TPageAnimationFinishEvent = procedure(Sender: TObject; NewPage: Integer) of object;
+  TCanBeginDragEvent = procedure(Sender: TObject; var CanBegin: Boolean) of object;
+
+  TFMXImageSlider = class;
+
+  TSliderDots = class(TControl)
+  private
+    FDotContainer: TLayout;
+    FDotSize: Single;
+    FActiveColor: TAlphaColor;
+    FInActiveColor: TAlphaColor;
+    FActiveIndex: Integer;
+    procedure SetDotCount(const Value: Integer);
+    function GetDotCount: Integer;
+    procedure CreateDotShape;
+    procedure SetDotSize(const Value: Single);
+    procedure SetActiveColor(const Value: TAlphaColor);
+    procedure SetInActiveColor(const Value: TAlphaColor);
+    procedure SetActiveIndex(const Value: Integer);
+  protected
+    procedure HitTestChanged; override;
+    function GetDot(Index: Integer): TControl;
+    {$IFDEF VER320_up}
+    procedure DoResized; override;
+    {$ELSE}
+    procedure Resize; override;
+    {$ENDIF}
+  public
+    constructor Create(AOwner: TComponent); override;
+    property ActiveIndex: Integer read FActiveIndex write SetActiveIndex;
+    property DotCount: Integer read GetDotCount write SetDotCount;
+    property DotSize: Single read FDotSize write SetDotSize;
+    property ActiveColor: TAlphaColor read FActiveColor write SetActiveColor;
+    property InActiveColor: TAlphaColor read FInActiveColor write SetInActiveColor;
+  end;
+
+  [ComponentPlatformsAttribute(TFMXPlatforms)]
+  TFMXImageSlider = class(TLayout)
+  private
+    FContainer: TControl;
+    FIsTimer: Boolean;
+    FAutoSlider: Boolean;
+    FTimer: TTimer;
+    FPages: TList<TControl>;
+    FActivePage: Integer;
+    FIsMove: Boolean;
+    FStartDrag: Boolean;
+    FBeforeDrag: Boolean;
+    FDownPos: TPointF;
+    FDownIndex: Integer;
+    FAnimation: TAnimation;
+    FOnPageAnimationFinish: TPageAnimationFinishEvent;
+    FOnCanDragBegin: TCanBeginDragEvent;
+    FOnItemTap: TTapEvent;
+    FOnItemClick: TNotifyEvent;
+    FOnPageChange: TPageChangeEvent;
+    FAnimationInterval: Integer;
+    FTransitionLayouts: array of TControl;
+    FTranstionIsIn: Boolean;
+    FTranstionStartX: Single;
+    FDots: TSliderDots;
+    FIsCanDrag: Boolean;
+    procedure MoveToActivePage(IsIn: Boolean = True);
+    procedure OnTimer(Sender: TObject);
+    procedure AnimationProcess(Sender: TObject);
+    procedure AnimationFinished(Sender: TObject);
+    function GetAnimateDuration: Single;
+    function GetDatas(Index: Integer): string;
+    function GetPageCount: Integer;
+    function GetTimerInterval: Integer;
+
+    procedure SetActivePage(const Value: Integer); { change }
+    procedure SetAnimateDuration(const Value: Single);
+    procedure SetAutoSlider(const Value: Boolean);
+    procedure SetPageCount(const Value: Integer);
+    procedure SetDatas(Index: Integer; const Value: string);
+    function SetDragBegin: Boolean;
+    function GetDotsVisible: Boolean;
+    procedure SetDotsVisible(const Value: Boolean);
+    function GetDotActiveColor: TAlphaColor;
+    function GetDotInActiveColor: TAlphaColor;
+    procedure SetDotActiveColor(const Value: TAlphaColor);
+    procedure SetDotInActiveColor(const Value: TAlphaColor);
+  protected
+    procedure SetTimerInterval(const Value: Integer);
+    {$IFDEF VER320_up}
+    procedure DoResized; override;
+    {$ELSE}
+    procedure Resize; override;
+    {$ENDIF}
+    procedure DoPageChange(NewPage, OldPage: Integer);
+    procedure DoTap(Sender: TObject; const Point: TPointF);
+    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
+    procedure MouseMove(Shift: TShiftState; X, Y: Single); override;
+    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
+    procedure AddImage(const Value: string; Image: TImageView);
+    procedure AddPage(const Value: string; Page: TControl);
+    procedure PrepareSlide(DeltaX: Single); overload;
+    procedure PrepareSlide(IsIn: Boolean); overload;
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor Destroy; override;
+    procedure SetPage(Index: Integer; AImage: TImageView);
+    procedure Add(Bitmap: TBitmap); overload;                //add bitmap
+    procedure Add(Value: string; Bitmap: TBitmap); overload;  //add bitmap and value
+    procedure Add(Value: string; Bitmap: TStream); overload;  //add bitmap stream and value
+    procedure Clear;  //Page Clear;
+    procedure Prev;   //Previous Page
+    procedure Next;   //Next Page
+    property Datas[Index: Integer]: string read GetDatas write SetDatas; //Page value(ex 0page = Datas[0])
+    property IsCanDrag: Boolean read FIsCanDrag write FIsCanDrag default True;
+  published
+    property Align;
+    property Height;
+    property Position;
+    property Width;
+    property ActivePage: Integer read FActivePage write SetActivePage;    //page move
+    property AnimateDuration: Single read GetAnimateDuration write SetAnimateDuration;
+    property AutoSlider: Boolean read FAutoSlider write SetAutoSlider;    //auto slider property
+    property DotActiveColor: TAlphaColor read GetDotActiveColor write SetDotActiveColor;
+    property DotInActiveColor: TAlphaColor read GetDotInActiveColor write SetDotInActiveColor;
+    property DotsVisible: Boolean read GetDotsVisible write SetDotsVisible;
+    property PageCount: Integer read GetPageCount write SetPageCount;
+    property TimerInterval: Integer read GetTimerInterval write SetTimerInterval; //auto slider timer
+    property OnCanDragBegin: TCanBeginDragEvent read FOnCanDragBegin write FOnCanDragBegin;
+    property OnItemClick: TNotifyEvent read FOnItemClick write FOnItemClick;     //page click event(use Desktop)
+    property OnItemTap: TTapEvent read FOnItemTap write FOnItemTap;       //page tab event(use Mobile, Pad)
+    property OnPageChange: TPageChangeEvent read FOnPageChange write FOnPageChange;
+    property OnPageAnimationFinish: TPageAnimationFinishEvent read FOnPageAnimationFinish write FOnPageAnimationFinish;
+  end;
+
+implementation
+
+type
+  TMyAnimation = class(TAnimation)
+  protected
+    procedure ProcessAnimation; override;
+  end;
+
+{ TFMXImageSlider }
+
+procedure TFMXImageSlider.Add(Bitmap: TBitmap);
+begin
+  Add('', Bitmap);
+end;
+
+procedure TFMXImageSlider.Add(Value: String; Bitmap: TBitmap);
+var
+  Img : TImageView;
+begin
+  Img := TImageView.Create(Self);
+  Img.Image.SetBitmap(TViewState.None, Bitmap);
+  AddImage(Value, Img);
+end;
+
+procedure TFMXImageSlider.Add(Value: String; Bitmap: TStream);
+var
+  Img : TImageView;
+  Bmp: TBitmap;
+begin
+  Img := TImageView.Create(Self);
+  Bmp := TBitmap.Create;
+  try
+    Bmp.LoadFromStream(Bitmap);
+    Img.Image.SetBitmap(TViewState.None, Bmp);
+  finally
+    FreeAndNil(Bmp);
+  end;
+  AddImage(Value, Img);
+end;
+
+procedure TFMXImageSlider.AddImage(const Value: string; Image: TImageView);
+var
+  Page: TLayout;
+begin
+  Page := TLayout.Create(Self);
+  Image.Stored    := False;
+  Image.ScaleType := TImageScaleType.CenterCrop;
+  Image.Parent    := Page;
+  Image.HitTest   := False;
+  Image.Align     := TAlignLayout.Client;
+  AddPage(Value, Page);
+end;
+
+procedure TFMXImageSlider.AddPage(const Value: string; Page: TControl);
+begin
+  Page.Parent := FContainer;
+  Page.SetBounds(0,0, FContainer.Width, FContainer.Height);
+  Page.Stored := False;
+  Page.Visible := False;
+  Page.TagString  := Value;
+  Page.Tag        := FPages.Add(Page);
+  FDots.DotCount := PageCount;
+end;
+
+procedure TFMXImageSlider.Clear;
+var
+  I: Integer;
+begin
+  for I := FPages.Count-1 downto 0 do
+  begin
+    FPages[I].DisposeOf;
+  end;
+  FPages.Clear;
+  ActivePage := -1;
+  FDots.DotCount := PageCount;
+end;
+
+constructor TFMXImageSlider.Create(AOwner: TComponent);
+begin
+  inherited;
+  FContainer := TControl.Create(Self);
+  FContainer.Align := TAlignLayout.Client;
+  FContainer.Stored := False;
+  FContainer.HitTest := False;
+  FContainer.ClipChildren := True;
+  FContainer.Parent := Self;
+
+  FDots := TSliderDots.Create(Self);
+  FDots.Stored := False;
+  FDots.SetBounds(0, FContainer.Height - FDots.Height, FContainer.Width, FDots.Height);
+  FDots.Parent := Self;
+
+  FTimer          := TTimer.Create(Self);
+  FTimer.Interval := 1000 * 5;
+  FTimer.Enabled  := False;
+  FTimer.OnTimer  := OnTimer;
+  FAutoSlider     := False;
+  FAnimation := TMyAnimation.Create(Self);
+  FAnimation.Stored := False;
+  FAnimation.Interpolation := TInterpolationType.Quintic;
+  FAnimation.Parent := Self;
+  FAnimation.Duration := 0.2;
+  FAnimation.OnProcess := AnimationProcess;
+  FAnimation.OnFinish := AnimationFinished;
+  FPages        := TList<TControl>.Create;
+  HitTest       := True;
+  FActivePage   := -1;
+  FStartDrag    := False;
+  AutoCapture   := True;
+
+  FIsCanDrag := True;
+end;
+
+destructor TFMXImageSlider.Destroy;
+begin
+  FPages.Free;
+  inherited;
+end;
+
+procedure TFMXImageSlider.DoPageChange(NewPage, OldPage: Integer);
+begin
+  if Assigned(FOnPageChange) then
+    FOnPageChange(Self, NewPage, OldPage);
+end;
+
+procedure TFMXImageSlider.DoTap(Sender: TObject; const Point: TPointF);
+begin
+  if Assigned(FOnItemTap) then FOnItemTap(Sender, Point);
+end;
+
+function TFMXImageSlider.GetAnimateDuration: Single;
+begin
+  Result := FAnimation.Duration;
+end;
+
+function TFMXImageSlider.GetDatas(Index: Integer): string;
+begin
+  Result := FPages[Index].TagString;
+end;
+
+function TFMXImageSlider.GetDotActiveColor: TAlphaColor;
+begin
+  Result := FDots.ActiveColor;
+end;
+
+function TFMXImageSlider.GetDotInActiveColor: TAlphaColor;
+begin
+  Result := FDots.InActiveColor;
+end;
+
+function TFMXImageSlider.GetDotsVisible: Boolean;
+begin
+  Result := FDots.Visible;
+end;
+
+function TFMXImageSlider.GetPageCount: Integer;
+begin
+  Result := FPages.Count;
+end;
+
+function TFMXImageSlider.GetTimerInterval: Integer;
+begin
+  Result := FTimer.Interval;
+end;
+
+procedure TFMXImageSlider.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
+begin
+  inherited;
+  FIsTimer := FTimer.Enabled;
+  if FIsTimer then
+    FTimer.Enabled := False;
+  FIsMove := False;
+  if IsCanDrag and (PageCount > 0) and (Button = TMouseButton.mbLeft) then
+  begin
+    FStartDrag := True;
+    FBeforeDrag := True;
+    FDownPos := PointF(X, Y);
+    FDownIndex := FActivePage;
+  end;
+end;
+
+procedure TFMXImageSlider.MouseMove(Shift: TShiftState; X, Y: Single);
+var
+  DeltaX: Single;
+begin
+  inherited;
+  if FStartDrag then
+  begin
+    if FBeforeDrag and not SetDragBegin then
+      exit;
+    if Abs(FDownPos.X - X) > 5 then
+      FIsMove := True;
+    DeltaX := X - FDownPos.X;
+    PrepareSlide(DeltaX);
+  end;
+end;
+
+procedure TFMXImageSlider.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
+var
+  DeltaX: Single;
+  PrevPage: Integer;
+begin
+  inherited;
+  if (not FIsMove) and FStartDrag then
+  begin
+    if FIsTimer then
+      FTimer.Enabled := True;
+    FStartDrag := False;
+    if Assigned(FOnItemClick) then
+      FOnItemClick(FPages[Self.ActivePage]);
+    if Assigned(FOnItemTap) then
+      FOnItemTap(FPages[Self.ActivePage], PointF(X, Y));
+    Exit;
+  end;
+  if FStartDrag then
+  begin
+    FStartDrag := False;
+    DeltaX := X - FDownPos.X;
+    if (Abs(DeltaX) > Width * 0.4) and (PageCount > 0) then
+    begin
+      if (DeltaX > 0) then
+      begin
+        PrevPage := FActivePage;
+        FActivePage := (FActivePage + PageCount - 1) mod PageCount;
+      end
+      else
+      begin
+        PrevPage := FActivePage;
+        FActivePage := (FActivePage + 1) mod PageCount;
+      end;
+      DoPageChange(FActivePage, PrevPage);
+      MoveToActivePage(DeltaX < 0);
+    end
+    else
+    begin
+      MoveToActivePage(DeltaX > 0);
+    end;
+    if FIsTimer then
+      FTimer.Enabled := True;
+  end;
+end;
+
+procedure TFMXImageSlider.MoveToActivePage(IsIn: Boolean);
+begin
+  PrepareSlide(IsIn);
+  FAnimation.Start;
+end;
+
+procedure TFMXImageSlider.Next;
+var
+  PrevPage: Integer;
+begin
+  if (PageCount < 2) or (FAnimation.Running) then Exit;
+  PrevPage := FActivePage;
+  FActivePage := (FActivePage + 1) mod PageCount;
+  DoPageChange(FActivePage, PrevPage);
+  MoveToActivePage(True);
+end;
+
+procedure TFMXImageSlider.OnTimer(Sender: TObject);
+begin
+  Next;
+end;
+
+procedure TFMXImageSlider.PrepareSlide(DeltaX: Single);
+var
+  Layout1, Layout2: TControl;
+  Index2: Integer;
+begin
+  Layout1 := FPages[FActivePage];
+  Layout1.Position.X := DeltaX;
+  if PageCount > 0 then
+  begin
+    if DeltaX > 0 then
+      // Find the left page
+      Index2 := (FActivePage + PageCount - 1) mod PageCount
+    else
+      Index2 := (FActivePage + 1) mod PageCount;
+    Layout2 := FPages[Index2];
+    Layout2.Visible := True;
+    if DeltaX > 0 then
+      Layout2.Position.X := Layout1.Position.X - Width
+    else
+      Layout2.Position.X := Layout1.BoundsRect.Right;
+  end;
+end;
+
+procedure TFMXImageSlider.PrepareSlide(IsIn: Boolean);
+var
+  Layout1, Layout2: TControl;
+  Index2: Integer;
+  X: Single;
+begin
+  FTranstionIsIn := IsIn;
+  Layout1 := FPages[FActivePage];
+  if Layout1.Visible then
+    FTranstionStartX := Layout1.Position.X
+  else
+  begin
+    if IsIn then
+      FTranstionStartX := Width
+    else
+      FTranstionStartX := -Width;
+    Layout1.SetBounds(FTranstionStartX, 0, Width, Height);
+    Layout1.Visible := true;
+  end;
+  if PageCount > 1 then
+  begin
+    if IsIn then
+      Index2 := (FActivePage + PageCount - 1) mod PageCount
+    else
+      Index2 := (FActivePage + 1) mod PageCount;
+    Layout2 := FPages[Index2];
+    Layout2.Visible := True;
+    if IsIn then
+      X := Layout1.Position.X - Width
+    else
+      X := Layout1.BoundsRect.Right;
+    Layout2.SetBounds(X, 0, Width, Height);
+    SetLength(FTransitionLayouts, 2);
+    FTransitionLayouts[0] := Layout1;
+    FTransitionLayouts[1] := Layout2;
+  end
+  else
+  begin
+    SetLength(FTransitionLayouts, 1);
+    FTransitionLayouts[0] := Layout1;
+  end;
+end;
+
+procedure TFMXImageSlider.Prev;
+var
+  PrevPage: Integer;
+begin
+  if (PageCount < 2) or (FAnimation.Running) then Exit;
+  PrevPage := FActivePage;
+  FActivePage := (FActivePage - 1 + PageCount) mod PageCount;
+  DoPageChange(FActivePage, PrevPage);
+  MoveToActivePage(False);
+end;
+
+{$IFDEF VER320_up}
+procedure TFMXImageSlider.DoResized;
+{$ELSE}
+procedure TFMXImageSlider.Resize;
+{$ENDIF}
+var
+  I: Integer;
+begin
+  inherited;
+  for I := 0 to FPages.Count - 1 do
+  begin
+    FPages[I].Width := FContainer.Width;
+    FPages[I].Height := FContainer.Height;
+  end;
+  FDots.SetBounds(0, FContainer.Height - FDots.Height, FContainer.Width, FDots.Height);
+end;
+
+procedure TFMXImageSlider.SetActivePage(const Value: Integer);
+var
+  IsIn: Boolean;
+  PrevPage, Delta: Integer;
+begin
+  if (Value < 0) or (Value > FPages.Count - 1) then // check if value valid
+    exit;
+  if FActivePage <> Value then
+  begin
+    PrevPage := FActivePage;
+    if FActivePage = -1 then
+    begin
+      FActivePage := Value;
+      FPages[FActivePage].SetBounds(0,0,Width,Height);
+      FPages[FActivePage].Visible := True;
+      DoPageChange(FActivePage, PrevPage);
+      FDots.ActiveIndex := FActivePage;
+    end
+    else
+    begin
+      Delta := Abs(FActivePage - Value);
+      // if current active page not neighbor of new active page, hide current page
+      if (Delta <> 1) and (Delta <> (PageCount-1)) then
+        FPages[FActivePage].Visible := False;
+      IsIn := FActivePage < Value;
+      FActivePage := Value; // set FActivePage
+      DoPageChange(FActivePage, PrevPage);
+      MoveToActivePage(IsIn);
+    end;
+  end;
+end;
+
+procedure TFMXImageSlider.SetAnimateDuration(const Value: Single);
+begin
+  FAnimation.Duration := Value;
+end;
+
+procedure TFMXImageSlider.AnimationFinished(Sender: TObject);
+begin
+  if FTransitionLayouts[0].Tag = FActivePage then
+  begin
+    if Length(FTransitionLayouts) = 2 then
+      FTransitionLayouts[1].Visible := False;
+  end
+  else
+  begin
+    FTransitionLayouts[0].Visible := False;
+  end;
+  If Assigned(FOnPageAnimationFinish) then
+    FOnPageAnimationFinish(Self, FActivePage);
+  FBeforeDrag := True;
+  FDots.ActiveIndex := FActivePage;
+end;
+
+procedure TFMXImageSlider.AnimationProcess(Sender: TObject);
+var
+  Start, Stop: Single;
+begin
+  Start := FTranstionStartX;
+  Stop := 0;
+  FTransitionLayouts[0].Position.X :=
+    InterpolateSingle(Start, Stop, FAnimation.NormalizedTime);
+  if Length(FTransitionLayouts) = 2 then
+  begin
+    if FTranstionIsIn then
+      FTransitionLayouts[1].Position.X :=  FTransitionLayouts[0].Position.X - Width
+    else
+      FTransitionLayouts[1].Position.X :=  FTransitionLayouts[0].BoundsRect.Right;
+  end;
+end;
+
+function TFMXImageSlider.SetDragBegin: Boolean;
+var
+  CanBeginDrag: Boolean;
+begin
+  Result := True;
+  if Assigned(FOnCanDragBegin) then
+  begin
+    FBeforeDrag := False;
+    FOnCanDragBegin(Self, CanBeginDrag);
+    Result := CanBeginDrag;
+  end;
+end;
+
+procedure TFMXImageSlider.SetAutoSlider(const Value: Boolean);
+begin
+  FAutoSlider := Value;
+  FTimer.Enabled := Value;
+end;
+
+procedure TFMXImageSlider.SetDatas(Index: Integer; const Value: string);
+begin
+  FPages[Index].TagString := Value;
+end;
+
+procedure TFMXImageSlider.SetDotActiveColor(const Value: TAlphaColor);
+begin
+  FDots.ActiveColor := Value;
+end;
+
+procedure TFMXImageSlider.SetDotInActiveColor(const Value: TAlphaColor);
+begin
+  FDots.InActiveColor := Value;
+end;
+
+procedure TFMXImageSlider.SetDotsVisible(const Value: Boolean);
+begin
+  FDots.Visible := Value;
+end;
+
+procedure TFMXImageSlider.SetPage(Index: Integer; AImage: TImageView);
+begin
+  if (Index >= 0) and (Index < PageCount) then
+  begin
+    AImage.HitTest := False;
+    AImage.Parent := FPages[Index];
+    AImage.Align := TAlignLayout.Client;
+  end;
+end;
+
+procedure TFMXImageSlider.SetPageCount(const Value: Integer);
+var
+  OldCount: Integer;
+  I: Integer;
+  L: TControl;
+begin
+  if Value <> PageCount then
+  begin
+    OldCount := PageCount;
+    if OldCount < Value then
+    begin
+      for I := OldCount + 1 to Value do
+      begin
+        AddPage('', TLayout.Create(Self));
+      end;
+    end
+    else if OldCount > Value then
+    begin
+      for I := OldCount - 1 downto Value do
+      begin
+        L := FPages[I];
+        L.DisposeOf;
+      end;
+      FPages.Count := Value;
+    end;
+    if Value > 0 then
+    begin
+      ActivePage := 0;
+    end
+    else
+    begin
+      ActivePage := -1;
+    end;
+  end;
+end;
+
+procedure TFMXImageSlider.SetTimerInterval(const Value: Integer);
+begin
+  FTimer.Interval := Value;
+end;
+
+{ TMyAnimation }
+
+procedure TMyAnimation.ProcessAnimation;
+begin
+end;
+
+{ TSliderDots }
+
+constructor TSliderDots.Create(AOwner: TComponent);
+begin
+  inherited;
+  FDotSize := 12;
+  FActiveIndex := -1;
+  FDotContainer := TLayout.Create(Self);
+  FDotContainer.Stored := False;
+  FDotContainer.Height := FDotSize;
+  FDotContainer.HitTest := False;
+  FDotContainer.Parent := Self;
+  FInActiveColor := $FFDDDDDD;
+  FActiveColor := $FF00B4FF;
+  HitTest := False;
+  Height := FDotSize * 2;
+end;
+
+procedure TSliderDots.CreateDotShape;
+var
+  Dot: TShape;
+  X, W: Single;
+  B: TRectF;
+begin
+  X := DotCount * DotSize * 2;
+  Dot := TCircle.Create(Self);
+  Dot.HitTest := False;
+  Dot.SetBounds(X, 0, DotSize, DotSize);
+  Dot.Stroke.Kind := TBrushKind.None;
+  Dot.Fill.Kind := TBrushKind.Solid;
+  Dot.Fill.Color := FInActiveColor;
+  Dot.Parent := FDotContainer;
+  X := (Self.Width - FDotContainer.Width) / 2;
+  W := (DotCount * 2 - 1) * DotSize;
+  B := TRectF.Create(X, 0, X + W, DotSize);
+  if Assigned(Scene) then //fixed by kngstr
+    B := B.SnapToPixel(Scene.GetSceneScale, False);
+  FDotContainer.BoundsRect := B;
+end;
+
+{$IFDEF VER320_up}
+procedure TSliderDots.DoResized;
+{$ELSE}
+procedure TSliderDots.Resize;
+{$ENDIF}
+var
+  X, W: Single;
+  B: TRectF;
+begin
+  inherited;
+  X := (Self.Width - FDotContainer.Width) / 2;
+  W := (DotCount * 2 - 1) * DotSize;
+  B := TRectF.Create(X, 0, X + W, DotSize);
+//  B := B.SnapToPixel(Scene.GetSceneScale, False);
+  FDotContainer.BoundsRect := B;
+end;
+
+
+function TSliderDots.GetDot(Index: Integer): TControl;
+begin
+  Result := TControl(FDotContainer.Children[Index]);
+end;
+
+function TSliderDots.GetDotCount: Integer;
+begin
+  Result := FDotContainer.ChildrenCount;
+end;
+
+procedure TSliderDots.HitTestChanged;
+begin
+  inherited;
+
+end;
+
+procedure TSliderDots.SetActiveColor(const Value: TAlphaColor);
+begin
+  if FActiveColor <> Value then
+  begin
+    FActiveColor := Value;
+    if (FActiveIndex >= 0) and (FActiveIndex < GetDotCount) then
+    begin
+      (GetDot(FActiveIndex) as TShape).Fill.Color := FActiveColor;
+    end;
+  end;
+end;
+
+procedure TSliderDots.SetActiveIndex(const Value: Integer);
+begin
+  if FActiveIndex <> Value then
+  begin
+    if (FActiveIndex >= 0) and (FActiveIndex < DotCount) then
+    begin
+      (GetDot(FActiveIndex) as TShape).Fill.Color := FInActiveColor;
+    end;
+    FActiveIndex := Value;
+    if (FActiveIndex >= 0) and (FActiveIndex < DotCount) then
+    begin
+      (GetDot(FActiveIndex) as TShape).Fill.Color := FActiveColor;
+    end;
+  end;
+end;
+
+procedure TSliderDots.SetDotCount(const Value: Integer);
+var
+  OldCount: Integer;
+  I: Integer;
+  Dot: TFMXObject;
+begin
+  if Value <> DotCount then
+  begin
+    OldCount := DotCount;
+    if OldCount < Value then
+    begin
+      for I := OldCount + 1 to Value do
+      begin
+        CreateDotShape;
+      end;
+    end
+    else
+    begin
+      for I := OldCount - 1 downto Value do
+      begin
+        Dot := FDotContainer.Children[I];
+        Dot.DisposeOf;
+      end;
+    end;
+  end;
+end;
+
+procedure TSliderDots.SetDotSize(const Value: Single);
+begin
+  if FDotSize <> Value then
+  begin
+    FDotSize := Value;
+    FDotContainer.Height := FDotSize;
+    Height := FDotSize * 3;
+  end;
+end;
+
+procedure TSliderDots.SetInActiveColor(const Value: TAlphaColor);
+var
+  I: Integer;
+  Dot: TShape;
+begin
+  if FInActiveColor <> Value then
+  begin
+    FInActiveColor := Value;
+    for I := 0 to DotCount-1 do
+    begin
+      if I <> FActiveIndex then
+      begin
+        Dot := GetDot(I) as TShape;
+        Dot.Fill.Color := FInActiveColor;
+      end;
+    end;
+  end;
+end;
+
+end.

+ 3600 - 0
FlyFilesUtils.pas

@@ -0,0 +1,3600 @@
+unit FlyFilesUtils;
+
+(* ************************************************ *)
+(*                         *)
+(*  设计:爱吃猪头肉 & Flying Wang 2013-11-30   *)
+(*      上面的版权声明请不要移除。      *)
+(*      Ver 1.0.2014.808            *)
+(*                         *)
+(* ************************************************ *)
+
+//1.0.2014.1108
+//支持 UTF8 的检查。
+
+//1.0.2014.908
+//支持 XE7。
+
+//1.0.2014.808
+//增加函数 IsPadOrPC。
+
+//1.0.2014.805
+//增加安卓下的获取 内存 SD 卡空间的函数。
+
+//1.0.2014.419
+//增加对 XE6 的支持。
+
+//1.0.2014.225
+//增加对 JNI 的接口查找功能。不完善。
+
+//1.0.2013.1219
+//在 亚瑟(Arthur)  3140223 的启发下,增加了更多的 SD 目录。
+
+//1.0.2013.1217
+//增加一个 FindSDCardSubPath 函数,用于查找指定的目录。
+
+//1.0.2013.1206
+//增加 [佛山]N.E(1024317)  9:36:38 提供的几个 SD 卡的路径。
+
+interface
+
+uses System.SysUtils,
+  System.Classes,
+{$IFDEF ANDROID}
+  Androidapi.JNIBridge,
+  Androidapi.IOUtils,
+{$ENDIF ANDROID}
+{$IFDEF MSWINDOWS}
+  Winapi.Windows,
+{$ENDIF MSWINDOWS}
+{$IFDEF POSIX}
+  Posix.Dlfcn, Posix.Fcntl, Posix.SysStat, Posix.SysTime, Posix.SysTypes, Posix.Locale,
+{$ENDIF POSIX}
+{$IFDEF PC_MAPPED_EXCEPTIONS}
+  System.Internal.Unwinder,
+{$ENDIF PC_MAPPED_EXCEPTIONS}
+{$IFDEF MACOS}
+  Macapi.Mach, Macapi.CoreServices, Macapi.CoreFoundation,
+{$ENDIF MACOS}
+  System.SysConst,
+  System.IOUtils;
+
+var
+  Error_NotFoundFileManager_Str: string = 'Not Found FileManager.';
+
+
+///	<summary>
+///	  返回大小写敏感的文件或路径名称
+///	</summary>
+///	<param name="FileName">
+///	  文件或路径名
+///	</param>
+///	<param name="RootPath">
+///	  <para>
+///	    检查路径的根目录
+///	  </para>
+///	  <para>
+///	    当大小写检查到此目录时停止,不再继续检查。
+///	  </para>
+///	</param>
+function GetCaseSensitiveFileName(const FileName: string; RootPath: string = ''): string;
+
+
+const
+  ///	<summary>
+  ///	  外置设备的数量
+  ///	</summary>
+  OTGDeivceCount = 16;
+
+  ///	<summary>
+  ///	  USB 磁盘,例如 U 盘、移动硬盘等
+  ///	</summary>
+  UsbDiskStartIndex = 255;
+
+  ///	<summary>
+  ///	  外置光驱
+  ///	</summary>
+  CDROMStartIndex = 255 + OTGDeivceCount;
+
+const
+  ///	<summary>
+  ///	  默认的删除等待时间,单位微秒
+  ///	</summary>
+  DeleteDirectories_WaitMinSecond = 2000;
+
+
+///	<summary>
+///	  检查 SD 卡或路径是否可用
+///	</summary>
+function isPathCanUseNow(const PathOrDir: string; const Default: Boolean = True): Boolean;
+
+///	<summary>
+///	  检查 SD 卡或路径是否写入
+///	</summary>
+function TestPathCanWrite(const PathOrDir: string): Boolean;
+
+///	<summary>
+///	  获取 手机存储 或 SD 卡的路径
+///	</summary>
+///	<param name="Index">
+///	  0 为 手机存储 1 为 SD 卡
+///	</param>
+///	<returns>
+///	  <para>
+///	    如果找到,返回路径。带 / 或 \
+///	  </para>
+///	  <para>
+///	    没找到,返回一个错误的路径。
+///	  </para>
+///	</returns>
+function GetSDCardPath(Index: Integer = 0): string;
+
+///	<summary>
+///	  查找 手机存储 或 SD 卡上的某个路径
+///	</summary>
+///	<param name="SubPath">
+///	  被查找的子路径
+///	</param>
+///	<param name="Index">
+///	  0 为 手机存储 1 为 SD 卡
+///	</param>
+///	<returns>
+///	  如果找到,返回路径。带 / 或 \ 没找到,返回一个错误的路径。
+///	</returns>
+function FindSDCardSubPath(SubPath: string; Index: Integer = 0): string;
+
+///	<summary>
+///	  获取当成工程的运行路径
+///	</summary>
+function GetAppPath: string;
+
+///	<summary>
+///	  查找一个路径下的指定格式的文件
+///	</summary>
+///	<param name="Path">
+///	  路径,必须用通配符结束。例如 /*
+///	</param>
+///	<param name="Attr">
+///	  需要查找的文件的属性
+///	</param>
+///	<param name="List">
+///	  返回一个文件名或目录名的列表
+///	</param>
+///	<param name="JustFile">
+///	  是否只查找文件
+///	</param>
+///	<returns>
+///	  无意义
+///	</returns>
+function BuildFileListInAPath(const Path: string; const Attr: Integer; const List: TStrings;
+  JustFile: Boolean = False): Boolean; overload;
+
+///	<summary>
+///	  查找一个路径下的指定格式的文件
+///	</summary>
+///	<param name="Path">
+///	  路径,必须用通配符结束。例如 /*
+///	</param>
+///	<param name="Attr">
+///	  需要查找的文件的属性
+///	</param>
+///	<param name="JustFile">
+///	  是否只查找文件
+///	</param>
+///	<returns>
+///	  返回换行分割的文件名或目录的列表
+///	</returns>
+function BuildFileListInAPath(const Path: string; const Attr: Integer;
+  JustFile: Boolean = False): string; overload;
+
+///	<summary>
+///	  查找指定路径下的文件
+///	</summary>
+///	<param name="DirName">
+///	  路径
+///	</param>
+///	<param name="SearchFilter">
+///	  通配符组成的查找格式
+///	</param>
+///	<param name="FileAttribs">
+///	  需要查找的文件的属性
+///	</param>
+///	<param name="isIncludeSubDirName">
+///	  是否包含子目录的名字
+///	</param>
+///	<param name="Recursion">
+///	  是否递归找子目录
+///	</param>
+///	<param name="FullName">
+///	  是否返回完整路径
+///	</param>
+///	<returns>
+///	  返回换行分割的文件名或目录的列表
+///	</returns>
+function GetFileNamesFromDirectory(const DirName: string; const SearchFilter: string = '*';
+  const FileAttribs: Integer = faAnyFile; const isIncludeSubDirName: Boolean = False; const Recursion: Boolean = False;
+  const FullName: Boolean = False): string;
+
+//可以用 TDirectory.Delete 代替下面的功能。
+///	<summary>
+///	  删除目录下指定的文件
+///	</summary>
+///	<param name="Source">
+///	  被删除的文件路径,可以使用通配符
+///	</param>
+///	<param name="AbortOnFailure">
+///	  失败时是否退出
+///	</param>
+///	<param name="YesToAll">
+///	  删掉所有文件,包括只读的。仅 WIN32 下有效。
+///	</param>
+///	<param name="WaitMinSecond">
+///	  检查文件删除的等待时间,单位 微秒
+///	</param>
+///	<returns>
+///	  是否删除完成
+///	</returns>
+function DeleteDirectoryByEcho(const Source: string;
+  AbortOnFailure: Boolean = False; YesToAll: Boolean = True;
+  WaitMinSecond: Integer = DeleteDirectories_WaitMinSecond): Boolean;
+
+  ///	<summary>
+///	  获取指定路径的总存储大小
+///	</summary>
+function GetTotalSpaceSize(Path: string = PathDelim): UInt64;
+///	<summary>
+///	  获取指定路径的可以使用的存储大小
+///	</summary>
+function GetAvailableSpaceSize(Path: string = PathDelim): UInt64;
+///	<summary>
+///	  获取指定路径的剩余(包括不可使用的)存储大小
+///	</summary>
+function GetFreeSpaceSize(Path: string = PathDelim): UInt64;
+
+///	<summary>
+///	  获取总内存大小
+{$IFDEF ANDROID}
+///   感谢[上海]故国(370620516)
+{$ENDIF}
+///	</summary>
+function GetTotalMemorySize: UInt64;
+///	<summary>
+///	  获取剩余内存大小
+{$IFDEF ANDROID}
+///   感谢[上海]故国(370620516)
+{$ENDIF}
+///	</summary>
+function GetFreeMemorySize: UInt64;
+
+///	<summary>
+///	  安卓 IOS 返回是否是 PAD(平板)
+///   其他平台,返回 True
+///   很多手机的 DPI 是错的,所以获取的尺寸也就不正常了,
+///   所以个别手机会被识别成 PAD。
+///	</summary>
+function IsPadOrPC: Boolean;
+//function IsPadOrPC(MiniScreenInches: Single = 6.2): Boolean;
+//function IsPad: Boolean;
+
+
+///	<summary>
+///	  在其他 APP 中打开文件。
+///	</summary>
+function OpenFileOnExtApp(const FileName: string; Https: Boolean = True): Boolean;
+
+function NowGMT_UTC: TDateTime;
+
+///	<summary>
+///	  获取完整 URL 的 Encode 结果。
+///   Just UTF8
+///	</summary>
+function EncodeURLWithSchemeOrProtocol(const URL: string): string;
+
+//上面是跨平台函数。
+//下面是平台函数。
+
+{$IFDEF ANDROID}
+
+function GetVolumePaths: string;
+
+function GetExternalStoragePath: string;
+
+//var
+//  ExterStoragePathCanRead: Boolean = True;
+//  ExterStoragePathCanWrite: Boolean = True;
+//  SDMountedMessageReceived: Boolean = False;
+function GetExterStoragePath: string;
+function GetInnerStoragePath: string;
+
+///	<summary>
+///	  It check SDCard0 Removable
+///	</summary>
+function GetIsExternalStorageRemovable: Boolean;
+
+///	<summary>
+///   很多手机的 DPI 是错的,所以获取的尺寸也就不正常了。
+///	</summary>
+function GetScreenClientInches: Single;
+
+///	<summary>
+///	  获取安卓下剩余内存大小
+///	</summary>
+//function GetActiveMemorySize: UInt64;
+
+///	<summary>
+///	  查找一个 JAVA 类是否可以使用
+///	</summary>
+///	<param name="NamePath">
+///	  类的全路径
+///	</param>
+function IsCanFindJavaClass(const NamePath: string): Boolean;
+function IsCanFindJavaMethod(const MethodName, Signature: string; const CalssNamePath: string = ''): Boolean;
+function IsCanFindJavaStaticMethod(const MethodName, Signature: string; const CalssNamePath: string = ''): Boolean;
+
+type
+  TGetFileNameListener = reference to procedure(const IsOK: Boolean; const FileName:string);
+  TGetFileNameLIsternerMethod = procedure (const IsOK: Boolean; const FileName:string) of object;
+
+function OpenFileDialog(Title, FileExtension:string; GetFileNameCallBack: TGetFileNameListener): Boolean; overload;
+function OpenFileDialog(Title, FileExtension:string; GetFileNameCallBack: TGetFileNameLIsternerMethod): Boolean; overload;
+
+function CheckPermission(const APermissionName: string): Boolean;
+
+const
+  C_android_permission_EXTERNAL_STORAGE = 'android.permission.WRITE_EXTERNAL_STORAGE';
+//  C_android_permission_WRITE_MEDIA = 'android.permission.WRITE_MEDIA_STORAGE';
+function CanWriteExterStorage: Boolean;
+
+/// <summary>
+///   更新相册
+/// </summary>
+procedure UpdateAlbum(FileNames: string);
+
+function ReadNoSizeFileToString(const AFileName: string): string;
+function ReadFileToString(const AFileName: string): string;
+
+{$ENDIF}
+
+implementation
+
+uses
+{$IFDEF ANDROID}
+{$IF CompilerVersion >= 27.0} // >= XE6
+  Androidapi.Helpers,
+//  FMX.Helpers.Android,
+{$ENDIF}
+{$IF CompilerVersion < 28.0} // < XE7
+  FMX.Helpers.Android,
+{$ENDIF}
+  Androidapi.Jni,
+  Androidapi.JNI.Environment,
+  Androidapi.JNI.StatFs,
+  Androidapi.JNI.Stream2,
+  Androidapi.JNI.ActivityManager,
+  Androidapi.JNI.JavaTypes,
+  Androidapi.NativeActivity,
+  Androidapi.JNI.GraphicsContentViewText,
+  Androidapi.JNI.Util,
+  Androidapi.JNI.android.os.storage.StorageManager,
+  Androidapi.JNI.java.lang.FlyUtils,
+  Androidapi.JNI.Webkit,
+//  Androidapi.JNI.Embarcadero,
+  Androidapi.JNI.App,
+  Androidapi.JNI.Net,
+  Androidapi.JNI.Media,
+  Androidapi.JNI.Provider,
+{$ENDIF}
+{$IF DEFINED(IOS) or DEFINED(MACOS)}
+  iOSapi.Foundation,
+  Macapi.ObjectiveC,
+  FMX.Helpers.iOS,
+//  iOSapi.UIDevice2,
+{$ENDIF}
+{$IFDEF MSWINDOWS}
+  Winapi.ShellApi,
+{$ENDIF}
+//  FMX.Dialogs,
+{$IF CompilerVersion >= 29.0} //  XE8
+  System.NetEncoding,
+{$ELSE}
+  IdURI,
+{$ENDIF}
+  System.Rtti,
+  System.TypInfo,
+  System.Messaging,
+  System.Math;
+
+
+//来自 inc 内部的 类型。
+{$IF DEFINED(IOS) or DEFINED(MACOS)}
+type
+{ Used by other time functions.  }
+  tm = record
+    tm_sec: Integer;            // Seconds. [0-60] (1 leap second)
+    tm_min: Integer;            // Minutes. [0-59]
+    tm_hour: Integer;           // Hours.[0-23]
+    tm_mday: Integer;           // Day.[1-31]
+    tm_mon: Integer;            // Month.[0-11]
+    tm_year: Integer;           // Year since 1900
+    tm_wday: Integer;           // Day of week [0-6] (Sunday = 0)
+    tm_yday: Integer;           // Days of year [0-365]
+    tm_isdst: Integer;          // Daylight Savings flag [-1/0/1]
+    tm_gmtoff: LongInt;         // Seconds east of UTC
+    tm_zone: MarshaledAString;  // Timezone abbreviation
+  end;
+  {$EXTERNALSYM tm}
+  Ptm = ^tm;
+{$ELSEIF DEFINED(ANDROID)}
+{ Used by other time functions.  }
+type
+  tm = record
+    tm_sec: Integer;            // Seconds. [0-60] (1 leap second)
+    tm_min: Integer;            // Minutes. [0-59]
+    tm_hour: Integer;           // Hours.[0-23]
+    tm_mday: Integer;           // Day.[1-31]
+    tm_mon: Integer;            // Month.[0-11]
+    tm_year: Integer;           // Year since 1900
+    tm_wday: Integer;           // Day of week [0-6] (Sunday = 0)
+    tm_yday: Integer;           // Days of year [0-365]
+    tm_isdst: Integer;          // Daylight Savings flag [-1/0/1]
+    tm_gmtoff: LongInt;         // Seconds east of UTC
+    tm_zone: MarshaledAString;         // Timezone abbreviation
+  end;
+  {$EXTERNALSYM tm}
+  Ptm = ^tm;
+{$ENDIF}
+
+
+//来自 inc 的函数定义。
+{$IFDEF POSIX}
+const
+{$IFDEF UNDERSCOREIMPORTNAME}
+  _PU = '_';
+{$ELSE}
+  _PU = '';
+{$ENDIF}
+const
+  libc        = '/usr/lib/libc.dylib';
+  libpthread  = '/usr/lib/libpthread.dylib';
+  libiconv    = '/usr/lib/libiconv.dylib';
+  libdl       = '/usr/lib/libdl.dylib';
+
+{$IF not Declared(_PU)}
+const
+  // On Mac OSX, cdecl names have a preceeding underscore
+  // if x86 native backend.
+  {$IF Defined(UNDERSCOREIMPORTNAME)}
+  _PU = '_';
+  {$ELSE}
+  _PU = '';
+  {$ENDIF}
+  {$EXTERNALSYM _PU}
+{$ENDIF}
+
+const
+{$IFNDEF IOS}
+  _INODE_SUFFIX = '$INODE64';
+{$ELSE IOS}
+  _INODE_SUFFIX = '';
+{$ENDIF !IOS}
+  {$EXTERNALSYM _INODE_SUFFIX}
+
+//具体函数定义开始。
+function _system(Name: MarshaledAString): Integer; cdecl;
+  external libc name _PU + 'system';
+
+function tempnam(const Path: MarshaledAString; const Prefix: MarshaledAString): MarshaledAString; cdecl;
+  external libc name _PU + 'tempnam';
+{$EXTERNALSYM tempnam}
+procedure free(p: Pointer); cdecl;
+  external libc name _PU + 'free';
+{$EXTERNALSYM free}
+function gettimeofday(var timeval: timeval; timezone: Pointer): Integer; cdecl;
+  external libc name _PU + 'gettimeofday';
+{$EXTERNALSYM gettimeofday}
+function gmtime_r(var Timer: time_t; var UnixTime: tm): Ptm; cdecl;
+  external libc name _PU + 'gmtime_r';
+{$EXTERNALSYM gmtime_r}
+{$ENDIF}
+
+//可以开始写函数了。
+
+function NowGMT_UTC: TDateTime;
+{$IFDEF MSWINDOWS}
+var
+  SystemTime: TSystemTime;
+begin
+  GetSystemTime(SystemTime);
+  Result := EncodeDate(SystemTime.wYear, SystemTime.wMonth, SystemTime.wDay) +
+    EncodeTime(SystemTime.wHour, SystemTime.wMinute, SystemTime.wSecond, SystemTime.wMilliseconds);
+end;
+{$ENDIF MSWINDOWS}
+{$IFDEF POSIX}
+var
+  T: time_t;
+  TV: timeval;
+  UT: tm;
+begin
+  gettimeofday(TV, nil);
+  T := TV.tv_sec;
+  gmtime_r(T, UT);
+  Result := EncodeDate(UT.tm_year + 1900, UT.tm_mon + 1, UT.tm_mday) +
+    EncodeTime(UT.tm_hour, UT.tm_min, UT.tm_sec, TV.tv_usec div 1000);
+end;
+{$ENDIF POSIX}
+
+function EncodeURLWithSchemeOrProtocol(const URL: string): string;
+var
+  Protocol: string;
+  AURL: string;
+  AIndex: Integer;
+begin
+{$IF CompilerVersion >= 29.0} //  XE8
+   AURL := URL.Trim;
+   Protocol := '';
+   AIndex := AURL.IndexOf('//');
+   if AIndex > 0 then
+   begin
+     Protocol := AURL.Substring(0, AIndex + 1); // has /
+     AURL := AURL.Substring(AIndex + 1); // has /
+   end;
+   Result := Protocol + TNetEncoding.URL.EncodePath(AURL);
+{$ELSE}
+   Result := TIdURI.URLEncode(Result);
+{$ENDIF}
+end;
+
+function EncodeURLWithOutSchemeOrProtocol(const URL: string; Https: Boolean = True): string;
+begin
+  Result := URL;
+  if FileExists(Result) then
+  begin
+{$IFDEF MSWINDOWS}
+{$ELSE}
+    if Result.Substring(0, 1) <> '/' then
+    begin
+      Result := '/' + Result;
+    end;
+    Result := 'file://' + Result;
+{$ENDIF}
+  end
+  else
+  begin
+    if Https then
+    begin
+      Result := 'https://' + URL;
+    end
+    else
+    begin
+      Result := 'http://' + URL;
+    end;
+    Result := EncodeURLWithSchemeOrProtocol(Result);
+  end;
+end;
+
+
+function OpenFileOnExtApp(const FileName: string; Https: Boolean = True): Boolean;
+{$IFDEF ANDROID}
+var
+  Intent: JIntent;
+  FileExtension: string;
+  mime: JMimeTypeMap;
+  MIMEType: JString;
+  TempStr,
+  FileToOpen: string;
+  AJFile: JFile;
+  AJUri: Jnet_Uri;
+begin
+// There may be an issue with the geo: prefix and URLEncode.
+// will need to research
+  Result := False;
+  if FileName = '' then
+    Exit;
+  FileExtension := AnsiLowerCase(ExtractFileExt(FileName));
+  if FileExtension = '' then
+    Exit;
+  mime := TJMimeTypeMap.JavaClass.getSingleton();
+  MIMEType := nil;
+  if mime <> nil then
+  begin
+    MIMEType := mime.getMimeTypeFromExtension(StringToJString(FileExtension.Substring(1)));
+  end;
+  if MIMEType <> nil then
+  begin
+    // 调用相应程序打开当前程序
+    Intent := TJIntent.Create;
+    Intent.setAction(TJIntent.JavaClass.ACTION_VIEW);
+    //TempStr := IncludeTrailingPathDelimiter(TPath.GetDocumentsPath)
+    TempStr := IncludeTrailingPathDelimiter(TPath.GetHomePath);
+{$IF CompilerVersion >= 33.0} //  RAD10.3
+    if FileExists(FileName) then
+    begin
+      FileToOpen := FileName;
+    end
+    else
+    begin
+      FileToOpen := EncodeURLWithOutSchemeOrProtocol(FileName, Https);
+    end;
+    if Pos(TempStr, FileToOpen) = 1 then
+    begin
+      AJFile := TJFile.JavaClass.init(StringToJString(FileToOpen));
+      AJUri := TAndroidHelper.JFileToJURI(AJFile);
+    end
+    else
+    begin
+{$ELSE}
+    begin
+{$ENDIF}
+      FileToOpen := EncodeURLWithOutSchemeOrProtocol(FileName, Https);
+      AJUri := StrToJURI(FileToOpen);
+    end;
+    Intent.setDataAndType(AJUri, MIMEType);
+    try
+      SharedActivity.startActivity(Intent);
+      Result := True;
+    except
+    end;
+  end;
+
+end;
+{$ELSE}
+{$IFDEF IOS}
+var
+  NSU: NSUrl;
+  AURL: string;
+begin
+  Result := False;
+  AURL := EncodeURLWithOutSchemeOrProtocol(FileName, Https);
+  // iOS doesn't like spaces, so URL encode is important.
+  NSU := StrToNSUrl(AURL);
+  if TOSVersion.Check(9) or SharedApplication.canOpenURL(NSU) then
+  try
+    Result := SharedApplication.openUrl(NSU);
+  except
+  end;
+end;
+{$ELSE}
+{$IFDEF MSWINDOWS}
+var
+  AURL: string;
+begin
+  Result := False;
+  AURL := EncodeURLWithOutSchemeOrProtocol(FileName, Https);
+  try
+    ShellExecute(GetActiveWindow, 'open', PChar(AURL), '', '', SW_MAXIMIZE);
+    Result := True;
+  except
+  end;
+end;
+{$ELSE}
+var
+  M:TMarshaller;
+  AURL: string;
+begin
+  Result := False;
+  //AURL := 'open -a Safari ' +  EncodeURLWithOutSchemeOrProtocol(AURL);
+  AURL := 'open ' +  EncodeURLWithOutSchemeOrProtocol(FileName, Https);
+  try
+    _system(M.AsAnsi(AURL, CP_UTF8).ToPointer);
+    Result := True;
+  except
+  end;
+//  raise Exception.Create('Not supported!');
+end;
+{$ENDIF MSWINDOWS}
+{$ENDIF IOS}
+{$ENDIF ANDROID}
+
+function IsPadOrPC: Boolean;
+{$IF DEFINED(IOS) or DEFINED(MACOS)}
+{$IFDEF IOS}
+begin
+  Result := IsPad;
+end;
+{$ELSE IOS}
+begin
+  Result := True;
+end;
+{$ENDIF IOS}
+{$ENDIF IOS or MACOS}
+{$IFDEF MSWINDOWS}
+begin
+  Result := True;
+end;
+{$ENDIF}
+{$IFDEF ANDROID}
+//var
+//  ScreenInches2,
+//  ScreenInches1,
+//  x,y: Double;
+//  dm: JDisplayMetrics;
+//begin
+//  Result := False;
+//  dm := GetJDisplayMetrics;
+//  if dm = nil then exit;
+//  x := dm.widthPixels;
+//  y := dm.heightPixels;
+//  try
+//    ScreenInches1 := Sqrt((x * x / dm.xdpi / dm.xdpi) + (y * y / dm.ydpi / dm.ydpi));
+//    ScreenInches2 := Sqr(x * x + y * Y ) / dm.densityDpi;
+//  except
+//    exit;
+//  end;
+//  Result := ScreenInches1 >= MiniScreenInches;
+//  if Result then
+//    Result := ScreenInches2 >= MiniScreenInches;
+//end;
+var
+  IsTablet: Boolean;
+begin
+  Result := False;
+  IsTablet := False;
+//  CallInUIThreadAndWaitFinishing(
+//  procedure
+//  begin
+    IsTablet := SharedActivity.getResources.getConfiguration.screenLayout and
+      TJConfiguration.JavaClass.SCREENLAYOUT_SIZE_MASK >= TJConfiguration.JavaClass.SCREENLAYOUT_SIZE_LARGE;
+//  end);
+  Result := IsTablet;
+end;
+{$ENDIF}
+
+function IsPad: Boolean;
+begin
+{$IFDEF MSWINDOWS}
+begin
+  //code by [龟山]Aone(1467948783)
+  Result := TOSVersion.Check(6, 1) and
+               (GetSystemMetrics(SM_TABLETPC) <> 0) and
+               ((GetSystemMetrics(SM_DIGITIZER) and NID_MULTI_INPUT) = NID_MULTI_INPUT);
+end;
+{$ELSE MSWINDOWS}
+{$IF DEFINED(IOS) or DEFINED(MACOS)}
+{$IFDEF IOS}
+begin
+  Result := IsPad;
+end;
+{$ELSE IOS}
+begin
+  Result := False;
+end;
+{$ENDIF IOS}
+{$ELSE} //IF DEFINED(IOS) or DEFINED(MACOS)
+  Result := IsPadOrPC;
+{$ENDIF IOS or MACOS}
+{$ENDIF MSWINDOWS}
+end;
+
+function GetTotalMemorySize: UInt64;
+{$IF DEFINED(IOS) or DEFINED(MACOS)}
+begin
+//  Result := TUIDevice2.Wrap(TUIDevice2.OCClass.currentDevice).totalMemory;
+  Result := NSRealMemoryAvailable;
+end;
+{$ENDIF}
+{$IFDEF MSWINDOWS}
+var
+  lpBuffer : TMEMORYSTATUSEX;
+begin
+  Result := 0;
+  ZeroMemory(@lpBuffer, Sizeof(TMEMORYSTATUSEX));
+  lpBuffer.dwLength := Sizeof(TMEMORYSTATUSEX);
+  GlobalMemoryStatusEx(lpBuffer);
+  Result := lpBuffer.ullTotalPhys;
+end;
+{$ENDIF}
+{$IFDEF ANDROID}
+var
+  Mgr: JActivityManager;
+  MgrNative: JObject;
+  MemInfo: JActivityManager_MemoryInfo;
+  AStrings: TStringList;
+  TempValue: string;
+  AReader: JReader;
+  ABufferedReader: JBufferedReader;
+begin
+  Result := 0;
+  MgrNative :=
+{$IF CompilerVersion >= 30.0} // >=RAD10
+    TAndroidHelper.Context
+{$ELSE}
+    SharedActivityContext
+{$ENDIF}
+    .getSystemService(TJContext.JavaClass.ACTIVITY_SERVICE);
+  if MgrNative <> nil then
+  begin
+    Mgr := TJActivityManager.Wrap((MgrNative as ILocalObject).GetObjectID);
+    MemInfo := TJActivityManager_MemoryInfo.JavaClass.init;
+    Mgr.getMemoryInfo(MemInfo);
+    try
+      Result := UInt64(MemInfo.totalMem);
+    except
+      //API level < 16
+      try
+        Result := UInt64(MemInfo.availMem);
+      except
+        Result := 0;
+      end;
+      if FileExists('/proc/meminfo') then
+      begin
+        AStrings := TStringList.Create;
+        try
+          AStrings.LineBreak := sLineBreak;
+          AStrings.NameValueSeparator := ':';
+          AStrings.Clear;
+          AReader := TJFileReader.JavaClass.init(StringToJString('/proc/meminfo')) as JReader;
+          ABufferedReader := TJBufferedReader.JavaClass.init(AReader);
+          repeat
+            TempValue := JStringToString(ABufferedReader.readLine);
+            if TempValue <> '' then
+            begin
+              AStrings.Add(TempValue);
+            end;
+          until (not ABufferedReader.ready);
+          ABufferedReader.close;
+          TempValue := AStrings.Values['MemTotal'].Trim;
+          AStrings.Clear;
+          AStrings.NameValueSeparator := ' ';
+          AStrings.Add(TempValue.Trim);
+          TempValue := AStrings.Names[0];
+//          ShowMessage(TempValue);
+          Result := StrToInt64Def(TempValue, Result div 1024) * 1024;
+        finally
+          FreeAndNil(AStrings);
+        end;
+      end;
+    end;
+  end;
+end;
+{$ENDIF}
+
+function GetFreeMemorySize: UInt64;
+{$IF DEFINED(IOS) or DEFINED(MACOS)}
+begin
+  //Result := Max(0, GetTotalMemorySize -
+  //  TUIDevice2.Wrap(TUIDevice2.OCClass.currentDevice).userMemory);
+  Result := NSRealMemoryAvailable;
+end;
+{$ENDIF}
+{$IFDEF MSWINDOWS}
+var
+  lpBuffer : TMEMORYSTATUSEX;
+begin
+  Result := 0;
+  ZeroMemory(@lpBuffer, Sizeof(TMEMORYSTATUSEX));
+  lpBuffer.dwLength := Sizeof(TMEMORYSTATUSEX);
+  GlobalMemoryStatusEx(lpBuffer);
+  Result := lpBuffer.ullAvailPhys;
+end;
+{$ENDIF}
+{$IFDEF ANDROID}
+var
+  Mgr: JActivityManager;
+  MgrNative: JObject;
+  MemInfo: JActivityManager_MemoryInfo;
+begin
+  Result := 0;
+  MgrNative :=
+{$IF CompilerVersion >= 30.0} // >=RAD10
+    TAndroidHelper.Context
+{$ELSE}
+    SharedActivityContext
+{$ENDIF}
+    .getSystemService(TJContext.JavaClass.ACTIVITY_SERVICE);
+  if MgrNative <> nil then
+  begin
+    Mgr := TJActivityManager.Wrap((MgrNative as ILocalObject).GetObjectID);
+    MemInfo := TJActivityManager_MemoryInfo.JavaClass.init;
+    Mgr.getMemoryInfo(MemInfo);
+    try
+      Result := UInt64(MemInfo.availMem);
+    except
+      Result := 0;
+    end;
+  end;
+end;
+{$ENDIF}
+
+function GetTotalSpaceSize(Path: string = PathDelim): UInt64;
+{$IF DEFINED(IOS) or DEFINED(MACOS)}
+var
+  Dict: NSDictionary;
+  P: Pointer;
+const
+  FoundationFwk: string = '/System/Library/Frameworks/Foundation.framework/Foundation';
+begin
+  Result := 0;
+  if DirectoryExists(Path) or FileExists(Path) then
+  begin
+  end
+  else
+  begin
+    raise Exception.Create('Path " ' + Path +  '" not found.');
+  end;
+  Dict := TNSFileManager.Wrap(TNSFileManager.OCClass.defaultManager).attributesOfFileSystemForPath(NSStr(Path), nil);
+  if Dict = nil then
+    Exit;
+  P := Dict.objectForKey((CocoaNSStringConst(FoundationFwk, 'NSFileSystemSize') as ILocalObject).GetObjectID);
+  if P <> nil then
+    Result := TNSNumber.Wrap(P).unsignedLongLongValue;
+end;
+{$ENDIF}
+{$IFDEF MSWINDOWS}
+var
+  lpFreeBytesAvailableToCaller,
+  lpTotalNumberOfBytes,
+  lpTotalNumberOfFreeBytes: Int64;
+begin
+  Result := 0;
+  if DirectoryExists(Path) or FileExists(Path) then
+  begin
+  end
+  else
+  begin
+    raise Exception.Create('Path " ' + Path +  '" not found.');
+  end;
+  lpTotalNumberOfBytes := MaxInt;
+  if GetDiskFreeSpaceEx(pchar(ExtractFileDrive(Path)),
+    lpFreeBytesAvailableToCaller,
+    lpTotalNumberOfBytes, @lpTotalNumberOfFreeBytes) then
+  begin
+    Result := UInt64(lpTotalNumberOfBytes);
+  end;
+end;
+{$ENDIF}
+{$IFDEF ANDROID}
+var
+  AJFile: JFile;
+  AJStatFs: JStatFs;
+begin
+  Result := 0;
+  if DirectoryExists(Path) or FileExists(Path) then
+  begin
+  end
+  else
+  begin
+    raise Exception.Create('Path " ' + Path +  '" not found.');
+  end;
+  AJFile := TJFile.JavaClass.init(StringToJString(Path));
+  if AJFile = nil then exit;
+  AJStatFs := TJStatFs.JavaClass.init(AJFile.getPath);
+  if AJStatFs = nil then exit;
+  Result := UInt64(AJStatFs.getBlockSize) * UInt64(AJStatFs.getBlockCount);
+end;
+{$ENDIF}
+
+function GetFreeSpaceSize(Path: string = PathDelim): UInt64;
+{$IF DEFINED(IOS) or DEFINED(MACOS)}
+var
+  Dict: NSDictionary;
+  P: Pointer;
+const
+  FoundationFwk: string = '/System/Library/Frameworks/Foundation.framework/Foundation';
+begin
+  Result := 0;
+  if DirectoryExists(Path) or FileExists(Path) then
+  begin
+  end
+  else
+  begin
+    raise Exception.Create('Path " ' + Path +  '" not found.');
+  end;
+  Dict := TNSFileManager.Wrap(TNSFileManager.OCClass.defaultManager).attributesOfFileSystemForPath(NSStr(Path), nil);
+  if Dict = nil then
+    Exit;
+  P := Dict.objectForKey((CocoaNSStringConst(FoundationFwk, 'NSFileSystemFreeSize') as ILocalObject).GetObjectID);
+  if P <> nil then
+    Result := TNSNumber.Wrap(P).unsignedLongLongValue;
+end;
+{$ENDIF}
+{$IFDEF MSWINDOWS}
+var
+  lpFreeBytesAvailableToCaller,
+  lpTotalNumberOfBytes,
+  lpTotalNumberOfFreeBytes: Int64;
+begin
+  Result := 0;
+  if DirectoryExists(Path) or FileExists(Path) then
+  begin
+  end
+  else
+  begin
+    raise Exception.Create('Path " ' + Path +  '" not found.');
+  end;
+  lpTotalNumberOfFreeBytes := MaxInt;
+  if GetDiskFreeSpaceEx(pchar(ExtractFileDrive(Path)),
+    lpFreeBytesAvailableToCaller,
+    lpTotalNumberOfBytes, @lpTotalNumberOfFreeBytes) then
+  begin
+    Result := UInt64(lpTotalNumberOfFreeBytes);
+  end;
+end;
+{$ENDIF}
+{$IFDEF ANDROID}
+var
+  AJFile: JFile;
+  AJStatFs: JStatFs;
+begin
+  Result := 0;
+  if DirectoryExists(Path) or FileExists(Path) then
+  begin
+  end
+  else
+  begin
+    raise Exception.Create('Path " ' + Path +  '" not found.');
+  end;
+  AJFile := TJFile.JavaClass.init(StringToJString(Path));
+  if AJFile = nil then exit;
+  AJStatFs := TJStatFs.JavaClass.init(AJFile.getPath);
+  if AJStatFs = nil then exit;
+  Result := UInt64(AJStatFs.getBlockSize) * UInt64(AJStatFs.getFreeBlocks);
+end;
+{$ENDIF}
+
+function GetAvailableSpaceSize(Path: string = PathDelim): UInt64;
+{$IF DEFINED(IOS) or DEFINED(MACOS)}
+begin
+  Result := GetFreeSpaceSize(Path);
+end;
+{$ENDIF}
+{$IFDEF MSWINDOWS}
+var
+  lpFreeBytesAvailableToCaller,
+  lpTotalNumberOfBytes,
+  lpTotalNumberOfFreeBytes: Int64;
+begin
+  Result := 0;
+  if DirectoryExists(Path) or FileExists(Path) then
+  begin
+  end
+  else
+  begin
+    raise Exception.Create('Path " ' + Path +  '" not found.');
+  end;
+  lpFreeBytesAvailableToCaller := MaxInt;
+  if GetDiskFreeSpaceEx(pchar(ExtractFileDrive(Path)),
+    lpFreeBytesAvailableToCaller,
+    lpTotalNumberOfBytes, @lpTotalNumberOfFreeBytes) then
+  begin
+    Result := UInt64(lpFreeBytesAvailableToCaller);
+  end;
+end;
+{$ENDIF}
+{$IFDEF ANDROID}
+var
+  AJFile: JFile;
+  AJStatFs: JStatFs;
+begin
+  Result := 0;
+  if DirectoryExists(Path) or FileExists(Path) then
+  begin
+  end
+  else
+  begin
+    raise Exception.Create('Path " ' + Path +  '" not found.');
+  end;
+  AJFile := TJFile.JavaClass.init(StringToJString(Path));
+  if AJFile = nil then exit;
+    AJStatFs := TJStatFs.JavaClass.init(AJFile.getPath);
+  if AJStatFs = nil then exit;
+    Result := UInt64(AJStatFs.getBlockSize) * UInt64(AJStatFs.getAvailableBlocks);
+end;
+{$ENDIF}
+
+{$IFDEF ANDROID}
+
+function CanWriteExterStorage: Boolean;
+begin
+  Result := False;
+  Result := CheckPermission(C_android_permission_EXTERNAL_STORAGE);
+//  if Result and TOSVersion.Check(4) then
+//  begin
+// // 没有效果,而且返回 总是 False
+//    Result := CheckPermission(C_android_permission_WRITE_MEDIA);
+//  end;
+end;
+
+//copy form FMX.AddressBook.Android;
+function CheckPermission(const APermissionName: string): Boolean;
+var
+  PackageName: JString;
+begin
+  PackageName := TAndroidHelper.Context.getPackageName;
+//  if TOSVersion.Check(6) then
+//  begin
+//    Result := TAndroidHelper.Context.checkSelfPermission(StringToJString(APermissionName)) =
+//      TJPackageManager.JavaClass.PERMISSION_GRANTED;
+//    exit;
+//  end;
+  Result := TAndroidHelper.Context.getPackageManager.checkPermission(StringToJString(APermissionName), PackageName) =
+    TJPackageManager.JavaClass.PERMISSION_GRANTED;
+end;
+
+function isExternalStorageDocument(URI: Jnet_Uri): Boolean;
+begin
+  Result := False;
+  if URI = nil then exit;
+  Result := URI.getAuthority.equals(StringToJString('com.android.externalstorage.documents'));
+end;
+
+function isDownloadsDocument(URI: Jnet_Uri): Boolean;
+begin
+  Result := False;
+  if URI = nil then exit;
+  Result := URI.getAuthority.equals(StringToJString('com.android.providers.downloads.documents'));
+end;
+
+function isMediaDocument(URI: Jnet_Uri): Boolean;
+begin
+  Result := False;
+  if URI = nil then exit;
+  Result := URI.getAuthority.equals(StringToJString('com.android.providers.media.documents'));
+end;
+
+const
+  FILE_SELECT_CODE = 0;
+
+var
+  FMessageChooserID: Integer = 0;
+  FGetFileNameCallBack1: TGetFileNameListener;
+  FGetFileNameCallBack2: TGetFileNameLIsternerMethod;
+
+procedure HandleActivityMessageforChooser(const Sender: TObject; const M: TMessage);
+var
+  MediaDocument,
+  ExternalStorageDocument,
+  IsOK: Boolean;
+  DocType,
+  DocIDs,
+  FileScheme,
+  FileName:string;
+  URI: Jnet_Uri;
+  cursor: JCursor;
+  projection: TJavaObjectArray<JString>;
+  column_index: Integer;
+  DocIDInfos: TArray<string>;
+  TempDocIDStr: JString;
+  selection: JString;
+  selectionArgs: TJavaObjectArray<JString>;
+begin
+  IsOK := False;
+  FileName := '';
+  IsOK := M is TMessageResultNotification;
+  if IsOK and (TMessageResultNotification(M).RequestCode = FILE_SELECT_CODE) then
+  begin
+    IsOK := (TMessageResultNotification(M).ResultCode = TJActivity.JavaClass.RESULT_OK);
+  end;
+  if IsOK then
+  begin
+    IsOK := TMessageResultNotification(M).Value <> nil;
+  end;
+  if IsOK then
+  begin
+    URI := TMessageResultNotification(M).Value.getData;
+    IsOK := URI <> nil;
+  end;
+  FileName := '';
+  if IsOK then
+  begin
+    FileScheme := JStringToString(URI.getScheme);
+    if TOSVersion.Check(4, 4) and TJDocumentsContract.JavaClass.isDocumentUri(
+{$IF CompilerVersion >= 30.0} // >=RAD10
+      TAndroidHelper.Context
+{$ELSE}
+      SharedActivityContext
+{$ENDIF}
+      , URI) then
+    begin
+      //http://blog.csdn.net/u011200844/article/details/43703593
+      ExternalStorageDocument := False;
+      MediaDocument := False;
+      selection := nil;
+      selectionArgs := nil;
+      IsOK := False;
+      if isDownloadsDocument(URI) then
+      begin
+        TempDocIDStr := TJDocumentsContract.JavaClass.getDocumentId(URI);
+        URI := TJContentUris.JavaClass.withAppendedId(
+          TJnet_Uri.JavaClass.parse(StringToJString('content://downloads/public_downloads')),
+          TJLong.JavaClass.valueOf(TempDocIDStr).longValue);
+        //后面会继续处理。
+      end
+      else
+      begin
+        ExternalStorageDocument := isExternalStorageDocument(URI);
+        if not ExternalStorageDocument then
+          MediaDocument := isMediaDocument(URI);
+      end;
+      if ExternalStorageDocument or MediaDocument then
+      begin
+        DocIDs := JStringToString(TJDocumentsContract.JavaClass.getDocumentId(URI));
+        DocIDInfos := DocIDs.Split([':']);
+        if Length(DocIDInfos) > 1 then
+        begin
+          DocType := DocIDInfos[0];
+          if ExternalStorageDocument then
+          begin
+            if SameText(DocType, 'primary') then
+            begin
+              FileName := GetSDCardPath(0) + DocIDInfos[1];
+              IsOK := True;
+            end
+            else
+            begin
+              //后面会继续处理。
+            end;
+          end
+          else if MediaDocument then
+          begin
+            if SameText(DocType, 'image') then
+            begin
+              URI := TJImages_Media.JavaClass.EXTERNAL_CONTENT_URI;
+              IsOK := True;
+            end
+            else if SameText(DocType, 'video') then
+            begin
+              URI := TJVideo_Media.JavaClass.EXTERNAL_CONTENT_URI;
+              IsOK := True;
+            end
+            else if SameText(DocType, 'audio') then
+            begin
+              URI := TJAudio_Media.JavaClass.EXTERNAL_CONTENT_URI;
+              IsOK := True;
+            end;
+            if IsOK then
+            begin
+              selection := StringToJString('_id=?');
+              selectionArgs := TJavaObjectArray<JString>.Create(1);
+              selectionArgs.Items[0] := StringToJString(DocIDInfos[1]);
+              IsOK := False;
+              //后面会继续处理。
+            end;
+          end;
+        end;
+      end;
+    end
+    else
+      IsOK := SameText('file', FileScheme);
+  end
+  else exit;
+  if IsOK and FileName.IsEmpty then
+  begin
+    FileName := JStringToString(URI.getPath);
+  end
+  else if not IsOK then       
+  begin
+    IsOK := SameText('content', FileScheme);
+    if IsOK then
+    begin
+      IsOK := False;
+      projection := TJavaObjectArray<JString>.Create(1);
+      projection.Items[0] := StringToJString('_data');
+      try
+        cursor :=
+{$IF CompilerVersion >= 30.0} // >=RAD10
+          TAndroidHelper.Context
+{$ELSE}
+          SharedActivityContext
+{$ENDIF}
+          .getContentResolver().query(URI, projection, selection, selectionArgs, nil);
+          if (cursor <> nil) then
+          begin
+            column_index := cursor.getColumnIndexOrThrow(StringToJString('_data'));
+            if cursor.moveToFirst then
+            begin
+              FileName := JStringToString(cursor.getString(column_index));
+              IsOK := True;
+            end;
+          end;
+      except
+        IsOK := False;
+      end;
+    end;
+//    if not IsOK then
+//    begin
+//      FileName := JStringToString(URI.getPath);
+//      FileName := FileName.Trim;
+//      IsOK := not FileName.IsEmpty;
+//      if not IsOK then
+//      begin
+//          IsOK := FileName.IndexOf(PathDelim) <> -1;
+//      end;
+//      end;
+//    end;
+  end;
+  if IsOK then
+  begin
+    FileName := FileName.Trim;
+    IsOK := not FileName.IsEmpty;
+  end;
+//  if IsOK then
+//  begin
+////    IsOK := FileExists(FileName);
+//    IsOK := FileName.IndexOf(PathDelim) <> -1;
+//  end;
+  if Assigned(FGetFileNameCallBack1) then
+    FGetFileNameCallBack1(IsOK, FileName);
+  if Assigned(FGetFileNameCallBack2) then
+    FGetFileNameCallBack2(IsOK, FileName);
+end;
+
+function InternalOpenFileDialog(Title, FileExtension:string;
+  GetFileNameCallBack1: TGetFileNameListener;
+  GetFileNameCallBack2: TGetFileNameLIsternerMethod): Boolean;
+var
+  MIMEType: JString;
+  mime: JMimeTypeMap;
+  Intent: JIntent;
+  IntentChooser: JIntent;
+  sMIMEType: string;
+begin
+  Result := False;
+  FGetFileNameCallBack1 := GetFileNameCallBack1;
+  FGetFileNameCallBack2 := GetFileNameCallBack2;
+  if FileExtension.Substring(0,1) = '.' then
+  begin
+    FileExtension := FileExtension.Substring(1);
+  end;
+  if FileExtension  = '*.*' then
+  begin
+    FileExtension := '*/*';
+  end;
+  MIMEType := nil;
+  sMIMEType := '';
+  if FileExtension = 'file/*' then
+  begin
+    sMIMEType := 'file/*';
+  end
+  else if FileExtension = '*' then
+  begin
+    if TOSVersion.Check(4, 4) then
+    begin
+      sMIMEType := '*/*';
+    end
+    else
+    begin
+      sMIMEType := 'file/*';
+    end;
+  end
+  else if FileExtension = '*/*' then
+  begin
+    sMIMEType := '*/*';
+  end
+  else if FileExtension = '' then
+  begin
+    sMIMEType := '*/*';
+  end
+  else
+  begin
+    FileExtension := AnsiLowerCase(FileExtension);
+    //http://www.oschina.net/code/snippet_1269559_25060
+    mime := TJMimeTypeMap.JavaClass.getSingleton;
+    if mime <> nil then
+    begin
+      MIMEType := mime.getMimeTypeFromExtension(StringToJString(FileExtension));
+    end;
+  end;
+  if MIMEType <> nil then
+  begin
+    sMIMEType := JStringToString(MIMEType).Trim;
+  end;
+  if sMIMEType.IsEmpty then
+    sMIMEType := '*/*'; // File/* 有时候打不开
+  //http://www.cnblogs.com/linlf03/archive/2013/08/19/3267732.html
+//  if TOSVersion.Check(4, 4) then
+//  begin
+//    Intent := TJIntent.JavaClass.init(TJIntent.JavaClass.ACTION_OPEN_DOCUMENT);
+//  end
+//  else
+  begin
+    Intent := TJIntent.JavaClass.init(TJIntent.JavaClass.ACTION_GET_CONTENT);
+  end;
+  Intent.setType(StringToJString(sMIMEType));
+  Intent.addCategory(TJIntent.JavaClass.CATEGORY_OPENABLE);
+  IntentChooser := TJIntent.JavaClass.createChooser(Intent, StrToJCharSequence(Title));
+  if FMessageChooserID <> 0 then
+    TMessageManager.DefaultManager.Unsubscribe(TMessageResultNotification, FMessageChooserID);
+  FMessageChooserID := 0;
+  FMessageChooserID := TMessageManager.DefaultManager.SubscribeToMessage(
+    TMessageResultNotification, HandleActivityMessageforChooser);
+  try
+{$IF CompilerVersion >= 30.0} // >=RAD10
+    TAndroidHelper.Activity
+{$ELSE}
+    SharedActivity
+{$ENDIF}
+    .startActivityForResult(IntentChooser, FILE_SELECT_CODE);
+    Result := True;
+  except
+    raise Exception.Create(Error_NotFoundFileManager_Str);
+  end;
+end;
+
+function OpenFileDialog(Title, FileExtension:string; GetFileNameCallBack: TGetFileNameListener): Boolean;
+begin
+  Result := InternalOpenFileDialog(Title, FileExtension, GetFileNameCallBack, nil);
+end;
+
+function OpenFileDialog(Title, FileExtension:string; GetFileNameCallBack: TGetFileNameLIsternerMethod): Boolean;
+begin
+  Result := InternalOpenFileDialog(Title, FileExtension, nil, GetFileNameCallBack);
+end;
+
+var
+  FExterStoragePath: string = '';
+  FInnerStoragePath: string = '';
+  FVolumePaths: string = '';
+  FSDRegisteredReceiver: Boolean = False;
+
+//type
+//  TSDBroadcastListener = class(TJavaLocal, JFMXBroadcastReceiverListener)
+//  private
+//    //[Weak] FTestObj: TForm1;  //TForm1 是拿来测试,不是必须的。
+//  public
+//    constructor Create;
+//    //看上去,下面的函数属于线程中
+//    procedure onReceive(context: JContext; intent: JIntent); cdecl;
+//  end;
+//
+//var
+//  //安卓下消息3元素。
+//  FSDBroadcastReceiver: JFMXBroadcastReceiver;
+//  FSDIntentFilter: JIntentFilter;
+//  FSDBroadcastListener: TSDBroadcastListener;
+//  //你的 APP 安卓对象。
+//  FActivity: JNativeActivity;
+//
+//
+//{ TSDBroadcastListener }
+//
+//constructor TSDBroadcastListener.Create;
+//begin
+//  inherited Create;
+//  InnerStoragePath := GetSDCardPath(0);
+//  ExterStoragePath := GetSDCardPath(1);
+//  ExterStoragePathCanRead := False;
+//  ExterStoragePathCanWrite := False;
+//  SDMountedMessageReceived := False;
+//end;
+//
+//procedure TSDBroadcastListener.onReceive(context: JContext; intent: JIntent);
+//var
+//  Action,
+//  ExternalPath,
+//  TempStr1, TempStr2, TempStr3,
+//  TempStr: string;
+//  AJFile: JFile;
+//begin
+//  //这里就是处理消息的地方。
+//  Action := JStringToString(intent.getAction);
+//  TempStr1 := JStringToString(TJIntent.JavaClass.ACTION_MEDIA_MOUNTED);
+//  TempStr2 := JStringToString(TJIntent.JavaClass.ACTION_MEDIA_UNMOUNTED);
+//  ExternalPath := IncludeTrailingPathDelimiter(GetExternalStoragePath);
+//  if SameText(Action, TempStr1) or SameText(Action, TempStr2) then
+//  begin
+//    TempStr := JStringToString(intent.getData.getPath);// 外置设备路径
+//    TempStr := IncludeTrailingPathDelimiter(TempStr);
+//    if TOSVersion.Check(5) and SameFileName(TempStr, ExternalPath) then
+//    begin
+//      //说明内外卡交换了。这是 5.0 的特点。
+//      InnerStoragePath := ExterStoragePath;
+//      ExterStoragePath := TempStr;
+//    end;
+//    if SameFileName(TempStr, ExterStoragePath) then
+//    begin
+//      if SameText(Action, TempStr1) then
+//      begin
+//        ExterStoragePathCanRead := True;
+//        AJFile := TJFile.JavaClass.init(
+//          StringToJString(ExcludeTrailingPathDelimiter(TempStr)));
+//        if AJFile <> nil then
+//        begin
+//          ExterStoragePathCanRead := AJFile.canRead;
+//          ExterStoragePathCanWrite := AJFile.canWrite;
+//        end;
+//        SDMountedMessageReceived := True;
+//      end;
+//    end;
+//  end;
+//  TempStr1 := JStringToString(TJIntent.JavaClass.ACTION_MEDIA_REMOVED);
+//  TempStr2 := JStringToString(TJIntent.JavaClass.ACTION_MEDIA_SHARED);
+//  TempStr3 := JStringToString(TJIntent.JavaClass.ACTION_MEDIA_EJECT);
+//  if SameText(Action, TempStr1) or
+//    SameText(Action, TempStr2) or SameText(Action, TempStr3) then
+//  begin
+//    TempStr := JStringToString(intent.getData.getPath);// 外置设备路径
+//    TempStr := IncludeTrailingPathDelimiter(TempStr);
+//    if SameFileName(TempStr, ExterStoragePath) then
+//    begin
+//      ExterStoragePathCanRead := False;
+//      ExterStoragePathCanWrite := False;
+//    end;
+//  end;
+//end;
+//
+//procedure RegisterSDReceiver;
+//begin
+//  if FSDRegisteredReceiver then exit;
+//  FActivity := TJNativeActivity.Wrap(PANativeActivity(System.DelphiActivity)^.clazz);
+//  //FActivity :=
+//{$IF CompilerVersion >= 30.0} // >=RAD10
+//    TAndroidHelper.Activity
+//{$ELSE}
+//    SharedActivity
+//{$ENDIF};
+//  FSDBroadcastListener := TSDBroadcastListener.Create;
+//  FSDBroadcastReceiver := TJFMXBroadcastReceiver.JavaClass.init(FSDBroadcastListener);
+//
+//  //http://blog.csdn.net/yigelangmandeshiren/article/details/8145059
+//  FSDIntentFilter := TJIntentFilter.JavaClass.init(TJIntent.JavaClass.ACTION_MEDIA_MOUNTED);
+//  FSDIntentFilter.setPriority(1000);// 设置最高优先级
+//  FSDIntentFilter.addAction(TJIntent.JavaClass.ACTION_MEDIA_EJECT);
+//  FSDIntentFilter.addAction(TJIntent.JavaClass.ACTION_MEDIA_REMOVED);
+//  FSDIntentFilter.addAction(TJIntent.JavaClass.ACTION_MEDIA_SHARED);
+//  FSDIntentFilter.addAction(TJIntent.JavaClass.ACTION_MEDIA_UNMOUNTED);
+//  FSDIntentFilter.addDataScheme(StringToJString('file'));
+//
+//  //注册消息接收器。别忘了 unregisterReceiver(FBroadcastReceiver);
+//  FActivity.registerReceiver(FSDBroadcastReceiver, FSDIntentFilter);
+//end;
+//
+//procedure UnRegisterSDReceiver;
+//begin
+//  if not FSDRegisteredReceiver then exit;
+//  FActivity.unregisterReceiver(FSDBroadcastReceiver);
+//  FreeAndNil(FSDBroadcastListener);
+//  FSDBroadcastReceiver := nil;
+//  FSDIntentFilter := nil;
+//  FActivity := nil;
+//end;
+
+function GetIsExternalStorageRemovable: Boolean;
+begin
+  Result := True;
+  try
+    Result := TJEnvironment.JavaClass.isExternalStorageRemovable;
+  except
+    //低版本可能发生错误。
+  end;
+end;
+
+function GetExterStoragePath: string;
+//var
+//  AJstr: JString;
+begin
+  if FExterStoragePath.Trim = '' then
+  begin
+    FExterStoragePath := GetSDCardPath(1);
+    if GetIsExternalStorageRemovable then
+    begin
+      FExterStoragePath := GetSDCardPath(0);
+    end;
+  end;
+  Result := FExterStoragePath.Trim;
+  if not DirectoryExists(ExcludeTrailingPathDelimiter(Result)) then
+    Result := '';
+//  AJstr := TJSystem.JavaClass.getenv(StringToJString('SECONDARY_STORAGE'));
+//  if AJstr <> nil then
+//    Result := IncludeTrailingPathDelimiter(JStringToString(AJstr));
+end;
+
+function GetInnerStoragePath: string;
+//var
+//  AJstr: JString;
+begin
+  if FInnerStoragePath.Trim = '' then
+  begin
+    FInnerStoragePath := GetSDCardPath(0);
+    if GetIsExternalStorageRemovable then
+    begin
+      FInnerStoragePath := GetSDCardPath(1);
+    end;
+  end;
+  Result := FInnerStoragePath.Trim;
+  if not DirectoryExists(ExcludeTrailingPathDelimiter(Result)) then
+    Result := '';
+
+//  ExterStoragePath := GetSDCardPath(1);
+//  AJstr := TJSystem.JavaClass.getenv(StringToJString('EXTERNAL_STORAGE'));
+//  if AJstr <> nil then
+//    Result := IncludeTrailingPathDelimiter(JStringToString(AJstr));
+end;
+
+function GetVolumePaths: string;
+var
+  Sm: JStorageManager;
+  SmNative: JObject;
+  AList: TStrings;
+  VolumePaths: TJavaObjectArray<JString>;
+  I: Integer;
+begin
+  Result := '';
+  if not IsCanFindJavaMethod('getVolumePaths',
+    '()[Ljava/lang/String;', 'android/os/storage/StorageManager') then
+  begin
+    //可以返回点别的。
+    exit;
+  end;
+  Sm := nil;
+  SmNative :=
+{$IF CompilerVersion >= 30.0} // >=RAD10
+    TAndroidHelper.Context
+{$ELSE}
+    SharedActivityContext
+{$ENDIF}
+    .getSystemService(TJContext.JavaClass.STORAGE_SERVICE);
+  if SmNative <> nil then
+  begin
+    Sm := TJStorageManager.Wrap((SmNative as ILocalObject).GetObjectID);
+  end;
+  //不打算兼容 2.X 了。
+  if (Sm <> nil) and TOSVersion.Check(4) then
+  begin
+    AList := TStringList.Create;
+    try
+      try
+        VolumePaths := Sm.getVolumePaths;
+        for I := 0 to VolumePaths.Length - 1 do
+        begin
+          AList.Add(JStringToString(VolumePaths.Items[I]));
+        end;
+      except
+      end;
+      VolumePaths := nil;
+      Result := AList.Text;
+      FVolumePaths := Result;
+    finally
+      FreeAndNil(AList);
+    end;
+  end;
+end;
+
+//function GetActiveMemorySize: UInt64;
+//var
+//  AStringVs,
+//  AStrings: TStringList;
+//  TempValue: string;
+//  ABufferedReader: JBufferedReader;
+//  V1, V2: UInt64;
+//begin
+//  Result := 0;
+//  if FileExists('/proc/meminfo') then
+//  begin
+//    AStrings := TStringList.Create;
+//    AStringVs := TStringList.Create;
+//    try
+//      AStrings.LineBreak := sLineBreak;
+//      AStrings.NameValueSeparator := ':';
+//      AStrings.Clear;
+//      ABufferedReader := TJBufferedReader.JavaClass.init(
+//        JReader(TJFileReader.JavaClass.init(StringToJString('/proc/meminfo'))));
+//      repeat
+//        TempValue := JStringToString(ABufferedReader.readLine);
+//        if TempValue <> '' then
+//        begin
+//          AStrings.Add(TempValue);
+//        end;
+//      until (not ABufferedReader.ready);
+//      ABufferedReader.close;
+//      V1 := 0;
+//      TempValue := AStrings.Values['Mapped'].Trim;
+//      AStringVs.Clear;
+//      AStringVs.NameValueSeparator := ' ';
+//      AStringVs.Add(TempValue.Trim);
+//      TempValue := AStringVs.Names[0];
+//      V2 := StrToInt64Def(TempValue, 0);
+//      V1 := V1 + V2;
+//      TempValue := AStrings.Values['Inactive'].Trim;
+//      AStringVs.Clear;
+//      AStringVs.NameValueSeparator := ' ';
+//      AStringVs.Add(TempValue.Trim);
+//      TempValue := AStringVs.Names[0];
+//      V2 := StrToInt64Def(TempValue, 0);
+//      V1 := V1 + V2;
+//      Result := V1 * 1024;
+//    finally
+//      FreeAndNil(AStringVs);
+//      FreeAndNil(AStrings);
+//    end;
+//  end;
+//  if Result = 0 then
+//  begin
+//    Result := GetFreeMemorySize;
+//  end;
+//end;
+
+function GetScreenClientInches: Single;
+var
+  x,y: Double;
+  dm: JDisplayMetrics;
+begin
+  Result := 3;
+  dm := GetJDisplayMetrics;
+  if dm = nil then exit;
+  x := dm.widthPixels;
+  y := dm.heightPixels;
+//  x := System.Math.Power(x / dm.xdpi, 2);
+//  y := System.Math.Power(y / dm.ydpi, 2);
+//  Result := Sqrt(x + y);
+  if (dm.densityDpi > dm.xdpi) and (dm.densityDpi > dm.ydpi) then
+  begin
+    Result := Sqrt(x * x + y * y ) / dm.densityDpi;
+  end
+  else
+  begin
+    Result := Sqrt((x * x / dm.xdpi / dm.xdpi) + (y * y / dm.ydpi / dm.ydpi));
+  end;
+end;
+
+function IsCanFindJavaClass(const NamePath: string): Boolean;
+var
+  PEnv: PJNIEnv;
+{$IF CompilerVersion < 30.0} // < RAD10
+  PActivity: PANativeActivity;
+{$ELSE}
+  ContextClass: JNIClass;
+{$ENDIF}
+  TempClass: JNIClass;
+  AMS: MarshaledAString;
+begin
+  try
+    TempClass := nil;
+{$IF CompilerVersion < 30.0} // < RAD10
+    PActivity := PANativeActivity(System.DelphiActivity);
+    PActivity^.vm^.AttachCurrentThread(PActivity^.vm, @PEnv, nil);
+{$ELSE}
+    PJavaVM(System.JavaMachine)^.AttachCurrentThread(System.JavaMachine, @PEnv, nil);
+    ContextClass := nil;
+    ContextClass := PEnv^.GetObjectClass(PEnv, System.JavaContext);
+{$ENDIF}
+    AMS := MarshaledAString(Utf8Encode(NamePath.Trim.Replace('.', '/', [rfReplaceAll])));
+    try
+    TempClass := PEnv^.FindClass(PEnv, AMS);
+    PEnv^.ExceptionClear(PEnv);
+    Result := TempClass <> nil;
+    finally
+      if TempClass <> nil then
+        PEnv^.DeleteLocalRef(PEnv, TempClass);
+{$IF CompilerVersion < 30.0} // < RAD10
+{$ELSE}
+      if ContextClass <> nil then
+        PEnv^.DeleteLocalRef(PEnv, ContextClass);
+{$ENDIF}
+    end;
+  except
+    Result := False;
+  end;
+end;
+
+function IsCanFindJavaMethod(const MethodName, Signature: string; const CalssNamePath: string = ''): Boolean;
+var
+  PEnv: PJNIEnv;
+{$IF CompilerVersion < 30.0} // < RAD10
+  PActivity: PANativeActivity;
+{$ELSE}
+  ContextClass: JNIClass;
+{$ENDIF}
+  ActivityClass: JNIClass;
+  GetMethod: JNIMethodID;
+  ASignature: string;
+  AMSMethodName, AMSSignature: MarshaledAString;
+begin
+  try
+    AMSMethodName := MarshaledAString(Utf8Encode(MethodName.Trim));
+
+{$IF CompilerVersion < 30.0} // < RAD10
+    PActivity := PANativeActivity(System.DelphiActivity);
+    PActivity^.vm^.AttachCurrentThread(PActivity^.vm, @PEnv, nil);
+{$ELSE}
+    PJavaVM(System.JavaMachine)^.AttachCurrentThread(System.JavaMachine, @PEnv, nil);
+    ContextClass := nil;
+    ContextClass := PEnv^.GetObjectClass(PEnv, System.JavaContext);
+{$ENDIF}
+    ActivityClass := nil;
+    try
+      if CalssNamePath.Trim <> '' then
+      begin
+        ASignature := CalssNamePath.Trim.Replace('.', '/', [rfReplaceAll]);
+        AMSSignature := MarshaledAString(Utf8Encode(ASignature));
+        ActivityClass := PEnv^.FindClass(PEnv, AMSSignature);
+        PEnv^.ExceptionClear(PEnv);
+      end
+      else
+      begin
+{$IF CompilerVersion < 30.0} // < RAD10
+        ActivityClass := PEnv^.GetObjectClass(PEnv, PActivity^.clazz);
+{$ELSE}
+        ActivityClass := PEnv^.GetObjectClass(PEnv, System.JavaContext);
+{$ENDIF}
+      end;
+      if ActivityClass <> nil then
+      begin
+        ASignature := Signature.Trim;
+        AMSSignature := MarshaledAString(Utf8Encode(ASignature));
+        GetMethod := PEnv^.GetMethodID(PEnv, ActivityClass, AMSMethodName, AMSSignature);
+        PEnv^.ExceptionClear(PEnv);
+        Result := GetMethod <> nil;
+      end;
+    finally
+      if ActivityClass <> nil then
+        PEnv^.DeleteLocalRef(PEnv, ActivityClass);
+{$IF CompilerVersion < 30.0} // < RAD10
+{$ELSE}
+      if ContextClass <> nil then
+        PEnv^.DeleteLocalRef(PEnv, ContextClass);
+{$ENDIF}
+    end;
+  except
+    Result := False;
+  end;
+end;
+
+function IsCanFindJavaStaticMethod(const MethodName, Signature: string; const CalssNamePath: string = ''): Boolean;
+var
+  PEnv: PJNIEnv;
+{$IF CompilerVersion < 30.0} // < RAD10
+  PActivity: PANativeActivity;
+{$ELSE}
+  ContextClass: JNIClass;
+{$ENDIF}
+  ActivityClass: JNIClass;
+  GetMethod: JNIMethodID;
+  ASignature: string;
+  AMSMethodName, AMSSignature: MarshaledAString;
+begin
+  try
+    AMSMethodName := MarshaledAString(Utf8Encode(MethodName.Trim));
+
+{$IF CompilerVersion < 30.0} // < RAD10
+    PActivity := PANativeActivity(System.DelphiActivity);
+    PActivity^.vm^.AttachCurrentThread(PActivity^.vm, @PEnv, nil);
+{$ELSE}
+    PJavaVM(System.JavaMachine)^.AttachCurrentThread(System.JavaMachine, @PEnv, nil);
+    ContextClass := nil;
+    ContextClass := PEnv^.GetObjectClass(PEnv, System.JavaContext);
+{$ENDIF}
+    ActivityClass := nil;
+    try
+      if CalssNamePath.Trim <> '' then
+      begin
+        ASignature := CalssNamePath.Trim.Replace('.', '/', [rfReplaceAll]);
+        AMSSignature := MarshaledAString(Utf8Encode(ASignature));
+        ActivityClass := PEnv^.FindClass(PEnv, AMSSignature);
+        PEnv^.ExceptionClear(PEnv);
+      end
+      else
+      begin
+{$IF CompilerVersion < 30.0} // < RAD10
+        ActivityClass := PEnv^.GetObjectClass(PEnv, PActivity^.clazz);
+{$ELSE}
+        ActivityClass := PEnv^.GetObjectClass(PEnv, System.JavaContext);
+{$ENDIF}
+      end;
+      if ActivityClass <> nil then
+      begin
+        ASignature := Signature.Trim;
+        AMSSignature := MarshaledAString(Utf8Encode(ASignature));
+        GetMethod := PEnv^.GetStaticMethodID(PEnv, ActivityClass, AMSMethodName, AMSSignature);
+        PEnv^.ExceptionClear(PEnv);
+        Result := GetMethod <> nil;
+      end;
+    finally
+      if ActivityClass <> nil then
+        PEnv^.DeleteLocalRef(PEnv, ActivityClass);
+{$IF CompilerVersion < 30.0} // < RAD10
+{$ELSE}
+      if ContextClass <> nil then
+        PEnv^.DeleteLocalRef(PEnv, ContextClass);
+{$ENDIF}
+    end;
+  except
+    Result := False;
+  end;
+end;
+
+procedure UpdateAlbum(FileNames: string);
+var
+  AJStrList:  TJavaObjectArray<JString>;
+  I: Integer;
+begin
+  With TStringList.Create do
+  begin
+    try
+      Text := FileNames;
+      AJStrList := TJavaObjectArray<JString>.Create(Count);
+      for I := 0 to Count - 1 do
+      begin
+        AJStrList.Items[I] := StringToJString(Strings[I]);
+      end;
+    finally
+      Free;
+    end;
+  end;
+  TJMediaScannerConnection.JavaClass.scanFile(TAndroidHelper.Context, AJStrList,
+    nil, nil);
+end;
+
+function ReadFileToString(const AFileName: string): string;
+var
+  AReader: JReader;
+  ABufferedReader: JBufferedReader;
+  TempValue: string;
+begin
+  Result := '';
+  if not FileExists(AFileName) then exit;
+  AReader := TJFileReader.JavaClass.init(StringToJString(AFileName)) as JReader;
+  ABufferedReader := TJBufferedReader.JavaClass.init(AReader);
+  repeat
+    TempValue := JStringToString(ABufferedReader.readLine);
+    if TempValue <> '' then
+    begin
+      Result := Result + sLineBreak + TempValue;
+    end;
+  until (not ABufferedReader.ready);
+  ABufferedReader.close;
+end;
+
+function ReadNoSizeFileToString(const AFileName: string): string;
+begin
+  Result := '';
+  if not FileExists(AFileName) then exit;
+  With TFileStream.Create(AFileName, fmOpenRead) do
+  try
+    if Size = -1 then exit;
+  finally
+    Free;
+  end;
+  Result := ReadFileToString(AFileName);
+end;
+
+{$ENDIF}
+
+{$IFDEF ANDROID}
+
+function GetJniPath(MethodName, Signature: MarshaledAString): string;
+var
+  PEnv: PJniEnv;
+  ActivityClass: JNIClass;
+  FileClass: JNIClass;
+  GetMethod: JNIMethodID;
+  GetPathMethod: JNIMethodID;
+  PActivity: PANativeActivity;
+  StrPathObject: JNIObject;
+  FileObject: JNIObject;
+begin
+
+  PActivity := PANativeActivity(System.DelphiActivity);
+  PActivity^.vm^.AttachCurrentThread(PActivity^.vm, @PEnv, nil);
+  ActivityClass := PEnv^.GetObjectClass(PEnv, PActivity^.clazz);
+
+  GetMethod := PEnv^.GetMethodID(PEnv, ActivityClass, MethodName, Signature);
+  FileObject := PEnv^.CallObjectMethodA(PEnv, PActivity^.clazz, GetMethod, PJNIValue(ArgsToJNIValues([nil])));
+  if FileObject = nil then
+    Exit('');
+  FileClass := PEnv^.GetObjectClass(PEnv, FileObject);
+
+  GetPathMethod := PEnv^.GetMethodID(PEnv, FileClass, 'getPath', '()Ljava/lang/String;');
+  StrPathObject := PEnv^.CallObjectMethodA(PEnv, FileObject, GetPathMethod, PJNIValue(ArgsToJNIValues([])));
+
+  Result := JNIStringToString(PEnv, StrPathObject);
+
+  PEnv^.DeleteLocalRef(PEnv, StrPathObject);
+  PEnv^.DeleteLocalRef(PEnv, FileClass);
+  PEnv^.DeleteLocalRef(PEnv, FileObject);
+  PEnv^.DeleteLocalRef(PEnv, ActivityClass);
+
+end;
+
+function JNIgetExternalStorageDirectory(SubPath: string): string;
+begin
+//  if SubPath <> '' then
+//  begin
+//    Result := IncludeTrailingPathDelimiter(
+//      GetJniPath('getExternalStorageDirectory', '()Landroid/os/Environment;')
+//      ) + SubPath;
+//  end
+//  else
+//  begin
+//    Result := GetJniPath('getExternalStorageDirectory', '()Landroid/os/Environment;');
+//  end;
+
+  try
+    if SubPath <> '' then
+    begin
+      Result := IncludeTrailingPathDelimiter(JStringToString(
+        TJEnvironment.JavaClass.getExternalStorageDirectory.getPath)) + SubPath;
+    end
+    else
+    begin
+      Result := JStringToString(TJEnvironment.JavaClass.getExternalStorageDirectory.getPath);
+    end;
+  except
+    //低版本可能发生错误。
+  end;
+end;
+
+function GetExternalStoragePath: string;
+begin
+  Result := IncludeTrailingPathDelimiter(JNIgetExternalStorageDirectory(''));
+end;
+
+//function isPathCanWrite(const PathOrDir: string; const Default: Boolean = True): Boolean;
+//var
+//  ADir: string;
+//begin
+//  Result := False;
+//  ADir := ExcludeTrailingPathDelimiter(PathOrDir);
+//  if not DirectoryExists(ADir) then exit;
+//  Result := True;
+//  try
+////明明无权限写入,却返回 canWrite 为 True
+//    if not TJFile.JavaClass.init(StringToJString(ADir)).canWrite then
+//    begin
+//      Result := False;
+//    end;
+//  except
+//    Result := False;
+//  end;
+//end;
+
+function isPathCanUseNow(const PathOrDir: string; const Default: Boolean = True): Boolean;
+var
+  ADir: string;
+begin
+  Result := False;
+  ADir := ExcludeTrailingPathDelimiter(PathOrDir);
+  if not DirectoryExists(ADir) then exit;
+  try
+//    //下面的代码不能正确的区分内外 SD。
+//    Result := TJEnvironment.JavaClass.getExternalStorageState.equals(TJEnvironment.JavaClass.MEDIA_MOUNTED);
+//    if GetExterStoragePath.Trim = '' then exit;
+    Result := True;
+//    if FindJavaMethod('getStorageState',
+//      '(Ljava/io/File;)Ljava/lang/String;', 'android/os/Environment') then
+//    begin
+//      // 这个接口 5.0 会死锁。还是不用了。
+//      if not TJEnvironment.JavaClass.getStorageState(
+//        TJFile.JavaClass.init(StringToJString(ADir))).equals(
+//          TJEnvironment.JavaClass.MEDIA_MOUNTED) then
+//      begin
+//        Result := False;
+//      end;
+//    end
+//    else
+    begin
+      if not TJFile.JavaClass.init(StringToJString(ADir)).canRead then
+      begin
+        Result := False;
+      end;
+    end;
+  except
+  end;
+end;
+{$ELSE}
+function isPathCanUseNow(const PathOrDir: string; const Default: Boolean = True): Boolean;
+begin
+  Result := False;
+  if not DirectoryExists(ExcludeTrailingPathDelimiter(PathOrDir)) then exit;
+  Result := Default;
+end;
+{$ENDIF ANDROID}
+
+function TestPathCanWrite(const PathOrDir: string): Boolean;
+var
+  ATempFile,
+  ADir: string;
+  AHandle: THandle;
+begin
+  Result := False;
+  ADir := ExcludeTrailingPathDelimiter(PathOrDir);
+  if not DirectoryExists(ADir) then exit;
+  repeat
+    ATempFile := System.IOUtils.TPath.GetTempFileName;
+    try
+      if FileExists(ATempFile) then
+        System.IOUtils.TFile.Delete(ATempFile);
+    except
+    end;
+    ATempFile := IncludeTrailingPathDelimiter(ADir) + ExtractFileName(ATempFile);
+  until not FileExists(ATempFile);
+//  ATempFile := GetTempFileName(ADir);
+//  if FileExists(ATempFile) then
+//  begin
+//    System.IOUtils.TFile.Delete(ATempFile);
+//  end;
+  try
+    AHandle := INVALID_HANDLE_VALUE;
+{$IF Defined(MSWINDOWS)}
+    AHandle := FileCreate(ATempFile, 0);
+{$ELSEIF Defined(POSIX)}
+    AHandle := FileCreate(ATempFile, FileAccessRights);
+{$ENDIF POSIX}
+    if AHandle = INVALID_HANDLE_VALUE then
+    begin
+      Result := False;
+    end
+    else
+    begin
+      Result := True;
+      FileClose(AHandle);
+    end;
+  except
+    Result := False;
+  end;
+  if FileExists(ATempFile) then
+  begin
+    System.IOUtils.TFile.Delete(ATempFile);
+    Result := True;
+  end;
+end;
+
+//function GetTempFileName(const ATempPath: string): string;
+//{$IFDEF MSWINDOWS}
+//var
+//  ErrCode: UINT;
+//begin
+//  SetLength(Result, MAX_PATH);
+//
+//  SetLastError(ERROR_SUCCESS);
+//  ErrCode := Winapi.Windows.GetTempFileName(PChar(
+//    IncludeTrailingPathDelimiter(ATempPath)
+//    ), 'tmp', 0, PChar(Result)); // DO NOT LOCALIZE
+//  if ErrCode = 0 then
+//    raise EInOutError.Create(SysErrorMessage(GetLastError));
+//
+//  SetLength(Result, StrLen(PChar(Result)));
+//  if FileExists(Result) then
+//  begin
+//    System.IOUtils.TFile.Delete(Result);
+//  end;
+//end;
+//{$ENDIF}
+//{$IFDEF POSIX}
+//var
+//  LTempPath: TBytes;
+//  M: TMarshaller;
+//  LRet: MarshaledAString;
+//begin
+////   char * tempnam(const char *dir, const char *pfx);
+//
+//  { Obtain a temporary file name }
+//  // This code changed from getting the temp name from the temp path via system
+//  // to get the temp name inside the specified temp path. We get the system temp path.
+////  LTempPath := TEncoding.UTF8.GetBytes(string(tmpnam(nil)));
+//  LRet := tempnam(MarshaledAString(M.AsUTF8(
+// //返回的路径没有包含 ATempPath
+//    IncludeTrailingPathDelimiter(ATempPath)
+//    ).ToPointer),nil);
+//  LTempPath := TEncoding.UTF8.GetBytes(string(LRet));
+//  free(LRet);
+//
+//  { Convert to UTF16 or leave blank on possible error }
+//  if LTempPath <> nil then
+//    Result := TEncoding.UTF8.GetString(LTempPath)
+//  else
+//    Result := '';
+//end;
+//{$ENDIF POSIX}
+
+
+{$IFDEF ANDROID}
+function FileSystemAttributes(const Path: string): TFileSystemAttributes;
+begin
+  Result := [fsSymLink, fsCaseSensitive];
+//  For android platform we can use the function PathConf on the same way
+//  that is used on IOS, but the problem is that for android we only can check
+//  _PC_2_SYMLINKS name, and that call is failing on version 2.3.3.
+
+end;
+{$ENDIF ANDROID}
+
+function ExpandFileNameCase2(const FileName, RootPath: string; out MatchFound: TFilenameCaseMatch): string;
+var
+  SR: System.SysUtils.TSearchRec;
+  FullPath, Name: string;
+  Status: Integer;
+{$IFDEF POSIX}
+  FoundOne: Boolean;
+  Scans: Byte;
+  FirstLetter, TestLetter: string;
+{$ENDIF POSIX}
+begin
+  Result := ExpandFileName(FileName);
+  MatchFound := mkNone;
+
+  if FileName = '' then // Stop for empty strings, otherwise we risk to get info infinite loop.
+    Exit;
+
+  FullPath := ExtractFilePath(Result);
+  Name := ExtractFileName(Result);
+
+  // if FullPath is not the root directory  (portable)
+  if not SameFileName(FullPath, IncludeTrailingPathDelimiter(ExtractFileDrive(FullPath))) then
+  begin  // Does the path need case-sensitive work?
+    Status := FindFirst(ExcludeTrailingPathDelimiter(FullPath), faAnyFile, SR);
+    System.SysUtils.FindClose(SR);   // close search before going recursive
+    if Status <> 0 then
+    begin
+      if SameFileName(IncludeTrailingPathDelimiter(FullPath), IncludeTrailingPathDelimiter(RootPath)) then
+      begin
+      end
+      else
+      begin
+        FullPath := ExcludeTrailingPathDelimiter(FullPath);
+        FullPath := ExpandFileNameCase2(FullPath, RootPath, MatchFound);
+        if MatchFound = mkNone then
+          Exit;    // if we can't find the path, we certainly can't find the file!
+      end;
+      FullPath := IncludeTrailingPathDelimiter(FullPath);
+    end;
+  end;
+
+  // Path is validated / adjusted.  Now for the file itself
+  try
+    if System.SysUtils.FindFirst(FullPath + Name, faAnyFile, SR)= 0 then    // exact match on filename
+    begin
+      if not (MatchFound in [mkSingleMatch, mkAmbiguous]) then  // path might have been inexact
+      begin
+        if Name = SR.Name then
+          MatchFound := mkExactMatch
+        else
+          MatchFound := mkSingleMatch;
+      end;
+      Exit(FullPath + SR.Name);
+    end;
+  finally
+    System.SysUtils.FindClose(SR);
+  end;
+
+{$IFDEF POSIX}
+{ Scan the directory.
+  To minimize the number of filenames tested, scan the directory
+  using upper/lowercase first letter + wildcard.
+  This results in two scans of the directory (particularly on Linux) but
+  vastly reduces the number of times we have to perform an expensive
+  locale-charset case-insensitive string compare.  }
+
+  FoundOne := False;
+
+  if (fsCaseSensitive in FileSystemAttributes(FullPath)) or
+     (fsCasePreserving in FileSystemAttributes(FullPath)) then
+  begin
+    // First, scan for lowercase first letter
+    FirstLetter := AnsiLowerCase(Name[Low(string)]);
+    for Scans := 0 to 1 do
+    begin
+      Status := FindFirst(FullPath + FirstLetter + '*', faAnyFile, SR);
+      while Status = 0 do
+      begin
+        if AnsiSameText(SR.Name, Name) then
+        begin
+          if FoundOne then
+          begin  // this is the second match
+            MatchFound := mkAmbiguous;
+            Exit;
+          end
+          else
+          begin
+            FoundOne := True;
+            Result := FullPath + SR.Name;
+          end;
+        end;
+        Status := FindNext(SR);
+      end;
+      FindClose(SR);
+      TestLetter := AnsiUpperCase(Name[Low(string)]);
+      if TestLetter = FirstLetter then
+        Break;
+      FirstLetter := TestLetter;
+    end;
+
+    if MatchFound <> mkAmbiguous then
+    begin
+      if FoundOne then
+        MatchFound := mkSingleMatch
+      else
+        MatchFound := mkNone;
+    end;
+  end;
+{$ENDIF POSIX}
+end;
+
+function BuildFileListInAPath(const Path: string; const Attr: Integer;
+  JustFile: Boolean = False): string;
+var
+  AList: TStringList;
+begin
+  Result := '';
+  AList := TStringList.Create;
+  try
+    BuildFileListInAPath(Path, Attr, AList, JustFile);
+    Result := AList.Text;
+  finally
+    FreeAndNil(AList);
+  end;
+end;
+
+function BuildFileListInAPath(const Path: string; const Attr: Integer; const List: TStrings;
+  JustFile: Boolean = False): Boolean;
+var
+  SearchRec: TSearchRec;
+  R: Integer;
+begin
+  Assert(List <> nil);
+  R := System.SysUtils.FindFirst(ExcludeTrailingPathDelimiter(Path), Attr, SearchRec);
+  Result := R = 0;
+  try
+    if Result then
+    begin
+      while R = 0 do
+      begin
+        if (SearchRec.Name <> '.') and (SearchRec.Name <> '..') then
+        begin
+          if ((SearchRec.Attr and faDirectory) = faDirectory) then
+          begin
+            if not JustFile then
+              List.Add(SearchRec.Name);
+          end
+          else
+            List.Add(SearchRec.Name);
+        end;
+        R := System.SysUtils.FindNext(SearchRec);
+      end;
+      Result := R = 0;
+    end;
+  finally
+    System.SysUtils.FindClose(SearchRec);
+  end;
+end;
+
+
+function faAttrToFileAttributes(
+  const Attributes: Integer): TFileAttributes;
+begin
+{$IFDEF MSWINDOWS}
+  Result := [];
+
+  if Attributes and faReadOnly <> 0 then
+    Include(Result, TFileAttribute.faReadOnly);
+  if Attributes and faHidden <> 0 then
+    Include(Result, TFileAttribute.faHidden);
+  if Attributes and faSysFile <> 0 then
+    Include(Result, TFileAttribute.faSystem);
+  if Attributes and faDirectory <> 0 then
+    Include(Result, TFileAttribute.faDirectory);
+  if Attributes and faArchive <> 0 then
+    Include(Result, TFileAttribute.faArchive);
+  if Attributes and faSymLink <> 0 then
+    Include(Result, TFileAttribute.faSymLink);
+  if Attributes and faNormal <> 0 then
+    Include(Result, TFileAttribute.faNormal);
+  if Attributes and faTemporary <> 0 then
+    Include(Result, TFileAttribute.faTemporary);
+  if Attributes and faCompressed <> 0 then
+    Include(Result, TFileAttribute.faCompressed);
+  if Attributes and faEncrypted <> 0 then
+    Include(Result, TFileAttribute.faEncrypted);
+{$ENDIF MSWINDOWS}
+{$IFDEF POSIX}
+  Result := [];
+  if Attributes and faDirectory <> 0 then
+    Include(Result, TFileAttribute.faDirectory);
+  if Attributes and faArchive <> 0 then
+    Include(Result, TFileAttribute.faNormal);
+  if Attributes and faSymLink <> 0 then
+    Include(Result, TFileAttribute.faSymLink);
+  if Attributes and faNormal <> 0 then
+    Include(Result, TFileAttribute.faNormal);
+{$ENDIF POSIX}
+end;
+
+{$IFDEF MSWINDOWS}
+{$ELSE}
+//好像不一定有效果。
+function FileSetAttr(const FileName: string; Attr: Integer; FollowLink: Boolean = True): Integer;
+var
+  AFileAttributes: TFileAttributes;
+begin
+//  Result := faInvalid;
+//  AFileAttributes := faAttrToFileAttributes(Attr);
+//  TFile.SetAttributes(FileName, AFileAttributes);
+  Result := 0;
+end;
+{$ENDIF MSWINDOWS}
+
+function DeleteTreeByEcho(const Source: string; AbortOnFailure: Boolean = False;
+  YesToAll: Boolean = True;
+  WaitMinSecond: Integer = DeleteDirectories_WaitMinSecond): Boolean;
+var
+  Files: TStringList;
+  LPath: string; // writable copy of Path
+  FileName: string;
+  I: Integer;
+  PartialResult: Boolean;
+  Attr: Integer;
+  isDir: Boolean;
+  ToDeleteDir: string;
+  T: TDateTime;
+  FWaitSecond, FWaitMinSecond: Integer;
+begin
+  Result := False;
+  if not DirectoryExists(ExtractFileDir(Source)) then
+  begin
+    exit;
+  end;
+  if Trim(Source) = PathDelim then
+  begin
+    exit;
+  end;
+  isDir := DirectoryExists(Source);
+  Result := True;
+  Files := TStringList.Create;
+  try
+    if isDir then
+    begin
+      LPath := IncludeTrailingPathDelimiter(Source);
+      ToDeleteDir := LPath + '\*.*';
+    end
+    else
+    begin
+      LPath := IncludeTrailingPathDelimiter(ExtractFilePath(Source));
+      ToDeleteDir := Source;
+    end;
+    BuildFileListInAPath(ToDeleteDir, faAnyFile, Files);
+    for I := 0 to Files.Count - 1 do
+    begin
+      FileName := LPath + PathDelim + Files[I];
+      PartialResult := True;
+      // If the current file is itself a directory then recursively delete it
+      Attr := FileGetAttr(FileName);
+      if (Attr <> faInvalid) and ((Attr and faDirectory) <> 0) then
+      begin
+        PartialResult := DeleteTreeByEcho(FileName, AbortOnFailure, YesToAll,
+          WaitMinSecond);
+      end
+      else
+      begin
+        if YesToAll then
+        begin
+          // Set attributes to normal in case it's a readonly file
+          PartialResult := FileSetAttr(FileName, faNormal) = 0;
+        end
+        else
+        begin
+          if ((Attr and faSysFile) <> 0) or ((Attr and faReadOnly) <> 0) or (Attr = faInvalid)
+          then
+          begin
+            PartialResult := False;
+          end;
+        end;
+        if PartialResult then
+          PartialResult := System.SysUtils.DeleteFile(FileName); //TFile.Delete()
+      end;
+      if not PartialResult then
+      begin
+        Result := False;
+        if AbortOnFailure then
+        begin
+          Break;
+        end;
+      end;
+    end;
+  finally
+    FreeAndNil(Files);
+  end;
+  if Result and isDir then
+  begin
+    if YesToAll then
+    begin
+      // Finally remove the directory itself
+      Result := FileSetAttr(LPath, faNormal or faDirectory) = 0;
+    end
+    else
+    begin
+      Attr := FileGetAttr(LPath);
+      if ((Attr and faSysFile) <> 0) or ((Attr and faReadOnly) <> 0) or (Attr = faInvalid) then
+      begin
+        Result := False;
+      end;
+    end;
+    if Result then
+    begin
+{$IOCHECKS OFF}
+      RmDir(LPath);
+      T := Now;
+      FWaitSecond := WaitMinSecond div 1000;
+      FWaitMinSecond := WaitMinSecond mod 1000;
+      while DirectoryExists(LPath) do
+      begin
+        if T + EncodeTime(0, 0, FWaitSecond, FWaitMinSecond) < now then
+        begin
+          break;
+        end;
+        Sleep(1);
+      end;
+{$IFDEF IOCHECKS_ON}
+{$IOCHECKS ON}
+{$ENDIF IOCHECKS_ON}
+      // Result := IOResult = 0;
+      Result := not DirectoryExists(LPath);
+      // if not Result then
+      // begin
+      // ShowMessage(LPath);
+      // end;
+    end;
+  end;
+end;
+
+function DeleteDirectoryByEcho(const Source: string;
+  AbortOnFailure: Boolean = False; YesToAll: Boolean = True;
+  WaitMinSecond: Integer = DeleteDirectories_WaitMinSecond): Boolean;
+var
+  T: TDateTime;
+  FWaitSecond, FWaitMinSecond: Integer;
+begin
+  Result := False;
+  if not DirectoryExists(ExtractFileDir(Source)) then
+  begin
+    exit;
+  end;
+  if Trim(Source) = PathDelim then
+  begin
+    exit;
+  end;
+  Result := DeleteTreeByEcho(Source, AbortOnFailure, YesToAll, WaitMinSecond);
+  if Result then
+  begin
+    T := Now;
+    FWaitSecond := WaitMinSecond div 1000;
+    FWaitMinSecond := WaitMinSecond mod 1000;
+    while DirectoryExists(Source) do
+    begin
+      if T + EncodeTime(0, 0, FWaitSecond, FWaitMinSecond) < now then
+      begin
+        break;
+      end;
+      Sleep(1);
+    end;
+  end;
+end;
+
+function GetFileNamesFromDirectory(const DirName: string; const SearchFilter: string = '*';
+  const FileAttribs: Integer = faAnyFile; const isIncludeSubDirName: Boolean = False; const Recursion: Boolean = False;
+  const FullName: Boolean = False): string;
+var
+  SubList, FileNameList: TStrings;
+  DirPath: string;
+  SearchRec: TSearchRec;
+  i: Integer;
+begin
+  Result := '';
+  if Trim(SearchFilter) = '' then
+  begin
+    exit;
+  end;
+  if DirectoryExists(DirName) then
+  begin
+    FileNameList := TStringList.Create;
+    try
+      try
+        DirPath := IncludeTrailingPathDelimiter(DirName);
+        // 得到该目录下指定类型文件的文件名
+        if System.SysUtils.FindFirst(DirPath + Trim(SearchFilter), FileAttribs, SearchRec) = 0 then
+        try
+          repeat
+            if isIncludeSubDirName or ((SearchRec.Attr and faDirectory) <> faDirectory) then
+            begin
+              if (SearchRec.Attr and faDirectory) = faDirectory then
+              begin
+                if (ExtractFileName(SearchRec.Name) = '.') or (ExtractFileName(SearchRec.Name) = '..') then
+                begin
+                  Continue;
+                end;
+              end;
+              if FullName then
+              begin
+                FileNameList.Add(ExpandFileName(SearchRec.Name));
+              end
+              else
+              begin
+                FileNameList.Add(ExtractFileName(SearchRec.Name));
+              end;
+            end;
+          until System.SysUtils.FindNext(SearchRec) <> 0;
+        finally
+          System.SysUtils.FindClose(SearchRec);
+        end;
+
+        // 递归该目录下所有子目录下的指定文件。
+        if Recursion and (System.SysUtils.FindFirst(DirPath + '*', faDirectory, SearchRec) = 0) then
+        try
+          repeat
+            if Recursion and ((SearchRec.Attr and faDirectory) = faDirectory) and
+              (SearchRec.Name <> '.') and (SearchRec.Name <> '..') then
+            begin
+              Result := GetFileNamesFromDirectory(DirPath + ExtractFileName(SearchRec.Name), SearchFilter,
+                FileAttribs, isIncludeSubDirName, Recursion, FullName);
+              SubList := TStringList.Create;
+              try
+                SubList.Clear;
+                SubList.Text := Result;
+                if not FullName then
+                begin
+                  for i := 0 to SubList.Count - 1 do
+                  begin
+                    SubList.Strings[i] := ExtractFileName(SearchRec.Name) + PathDelim + SubList.Strings[i];
+                  end;
+                end;
+                Result := '';
+                FileNameList.AddStrings(SubList);
+              finally
+                FreeAndNil(SubList);
+              end;
+            end;
+          until System.SysUtils.FindNext(SearchRec) <> 0;
+        finally
+          System.SysUtils.FindClose(SearchRec);
+        end;
+        Result := FileNameList.Text;
+      except
+        Result := '';
+      end;
+    finally
+      if FileNameList <> nil then
+      begin
+        FreeAndNil(FileNameList);
+      end;
+    end;
+  end;
+end;
+
+
+function GetCaseSensitiveFileName(const FileName: string; RootPath: string = ''): string;
+var
+  MatchFound: TFilenameCaseMatch;
+begin
+  Result := ExpandFileNameCase2(FileName, RootPath, MatchFound);
+end;
+
+function GetSDCardPath(Index: Integer = 0): string;
+begin
+  Result := FindSDCardSubPath('', Index);
+end;
+
+function FindSDCardSubPath(SubPath: string; Index: Integer = 0): string;
+var
+  PathDelimedSubPath: string;
+  UnPathDelimedSubPath: string;
+{$IFDEF ANDROID}
+  VolumePathList: TStrings;
+  UsbIndex,
+  CdRomIndex,
+  TempVolumeIndex,
+  I: Integer;
+  IsFoundDir0: Boolean;
+{$ENDIF ANDROID}
+begin
+  PathDelimedSubPath := '';
+  UnPathDelimedSubPath := SubPath;
+  if UnPathDelimedSubPath <> '' then
+  begin
+    if UnPathDelimedSubPath.Chars[0] = PathDelim then
+    begin
+      UnPathDelimedSubPath := UnPathDelimedSubPath.Substring(1);
+    end;
+    PathDelimedSubPath := PathDelim + UnPathDelimedSubPath;
+  end;
+{$IFDEF ANDROID}
+  Result := '/storage/emulated/' + IntToStr(Index) + PathDelimedSubPath;
+  if FVolumePaths.Trim = '' then GetVolumePaths;
+  VolumePathList := TStringList.Create;
+  try
+    VolumePathList.Text := FVolumePaths;
+    if Index = 0 then
+    begin
+      Result := JNIgetExternalStorageDirectory(UnPathDelimedSubPath);
+
+      if not DirectoryExists(Result) and (VolumePathList.Count > 0) then
+      begin
+        Result := VolumePathList[0];
+      end;
+
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/storage/emulated/' + IntToStr(Index) + PathDelimedSubPath;
+      end;
+
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/storage/sdcard0' + PathDelimedSubPath;
+      end;
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/mnt/sdcard0' + PathDelimedSubPath;
+      end;
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/storage/sdcard' + PathDelimedSubPath;
+      end;
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/mnt/sdcard' + PathDelimedSubPath;
+      end;
+
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/storage/sdcard_ext0' + PathDelimedSubPath;
+      end;
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/storage/sdcard_ext' + PathDelimedSubPath;
+      end;
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/sdcard' + PathDelimedSubPath;
+      end;
+
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/mnt/sdcard_ext0' + PathDelimedSubPath;
+      end;
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/mnt/sdcard_ext' + PathDelimedSubPath;
+      end;
+
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/emmc' + PathDelimedSubPath;
+      end;
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/nand' + PathDelimedSubPath;
+      end;
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/flash' + PathDelimedSubPath;
+      end;
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/mnt/emmc' + PathDelimedSubPath;
+      end;
+
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/mnt/flash' + PathDelimedSubPath;
+      end;
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/storage/flash' + PathDelimedSubPath;
+      end;
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/mnt/D' + PathDelimedSubPath;
+      end;
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/storage/D' + PathDelimedSubPath;
+      end;
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/mnt/flash' + PathDelimedSubPath;
+      end;
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/storage/flash' + PathDelimedSubPath;
+      end;
+
+      //last
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/storage/sdcard0' + PathDelimedSubPath;
+      end;
+    end
+    else if (Index < UsbDiskStartIndex) then
+    begin
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/storage/sdcard' + IntToStr(Index) + PathDelimedSubPath;
+      end;
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/storage/sdcard' + IntToStr(Index + 1) + PathDelimedSubPath;
+      end;
+
+      if not DirectoryExists(Result) then
+      begin
+        Result := 'sdcard' + IntToStr(Index) + PathDelimedSubPath;
+      end;
+      if not DirectoryExists(Result) then
+      begin
+        Result := 'sdcard' + IntToStr(Index + 1) + PathDelimedSubPath;
+      end;
+
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/storage/sdcard_ext' + IntToStr(Index) + PathDelimedSubPath;
+      end;
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/storage/sdcard_ext' + IntToStr(Index + 1) + PathDelimedSubPath;
+      end;
+
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/mnt/sdcard' + IntToStr(Index) + PathDelimedSubPath;
+      end;
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/mnt/sdcard' + IntToStr(Index + 1) + PathDelimedSubPath;
+      end;
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/mnt/sdcard_ext' + IntToStr(Index) + PathDelimedSubPath;
+      end;
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/mnt/sdcard_ext' + IntToStr(Index + 1) + PathDelimedSubPath;
+      end;
+
+      if not DirectoryExists(Result) and (VolumePathList.Count > Index) then
+      begin
+        Result := VolumePathList[Index];
+        if (Result.IndexOf('usbcd') >= 0) or
+        (Result.IndexOf('usb') >= 0) or
+        (Result.IndexOf('otg') >= 0) or
+        (Result.IndexOf('-rw') >= 0) or
+        (Result.IndexOf('_rw') >= 0) or
+        (Result.IndexOf('cdrom') >= 0) or
+        (Result.IndexOf('cd_rom') >= 0) or
+        (Result.IndexOf('cd-rom') >= 0) then
+        begin
+          Result := '';
+        end;
+      end;
+
+      if Index = 1 then
+      begin
+        if not DirectoryExists(Result) and (VolumePathList.Count = 2) then
+        begin
+          Result := VolumePathList[1];
+          if (Result.IndexOf('/storage/emulated/') < 0) and
+            (Result.IndexOf('/storage/sdcard') < 0) and
+            (Result.IndexOf('/mnt/sdcard') < 0) then
+          begin
+            Result := '';
+          end;
+        end;
+
+        if not DirectoryExists(Result) then
+        begin
+          Result := '/storage/extSdCard' + PathDelimedSubPath;
+        end;
+        if not DirectoryExists(Result) then
+        begin
+          Result := '/storage/external1' + PathDelimedSubPath;
+        end;
+        if not DirectoryExists(Result) then
+        begin
+          Result := '/storage/sdcard-ext' + PathDelimedSubPath;
+        end;
+        if not DirectoryExists(Result) then
+        begin
+          Result := '/storage/ext_card' + PathDelimedSubPath;
+        end;
+        if not DirectoryExists(Result) then
+        begin
+          Result := '/storage/extsd' + PathDelimedSubPath;
+        end;
+        if not DirectoryExists(Result) then
+        begin
+          Result := '/storage/_ExternalSD' + PathDelimedSubPath;
+        end;
+        if not DirectoryExists(Result) then
+        begin
+          Result := '/storage/external_sd' + PathDelimedSubPath;
+        end;
+        if not DirectoryExists(Result) then
+        begin
+          Result := '/storage/removable_sdcard' + PathDelimedSubPath;
+        end;
+
+        if not DirectoryExists(Result) then
+        begin
+          Result := '/mnt/extSdCard' + PathDelimedSubPath;
+        end;
+        if not DirectoryExists(Result) then
+        begin
+          Result := '/mnt/external1' + PathDelimedSubPath;
+        end;
+        if not DirectoryExists(Result) then
+        begin
+          Result := '/mnt/sdcard-ext' + PathDelimedSubPath;
+        end;
+        if not DirectoryExists(Result) then
+        begin
+          Result := '/mnt/ext_card' + PathDelimedSubPath;
+        end;
+        if not DirectoryExists(Result) then
+        begin
+          Result := '/mnt/extsd' + PathDelimedSubPath;
+        end;
+        if not DirectoryExists(Result) then
+        begin
+          Result := '/mnt/_ExternalSD' + PathDelimedSubPath;
+        end;
+        if not DirectoryExists(Result) then
+        begin
+          Result := '/mnt/external_sd' + PathDelimedSubPath;
+        end;
+        if not DirectoryExists(Result) then
+        begin
+          Result := '/mnt/removable_sdcard' + PathDelimedSubPath;
+        end;
+
+        if not DirectoryExists(Result) then
+        begin
+          Result := 'tflash' + PathDelimedSubPath;
+        end;
+        if not DirectoryExists(Result) then
+        begin
+          Result := 'removable_sdcard' + PathDelimedSubPath;
+        end;
+
+        if not DirectoryExists(Result) then
+        begin
+          Result := GetSDCardPath(0) + 'tflash' + PathDelimedSubPath;
+        end;
+        if not DirectoryExists(Result) then
+        begin
+          Result := GetSDCardPath(0) + 'extSdCard' + PathDelimedSubPath;
+        end;
+        if not DirectoryExists(Result) then
+        begin
+          Result := GetSDCardPath(0) + 'external1' + PathDelimedSubPath;
+        end;
+        if not DirectoryExists(Result) then
+        begin
+          Result := GetSDCardPath(0) + 'sdcard-ext' + PathDelimedSubPath;
+        end;
+        if not DirectoryExists(Result) then
+        begin
+          Result := GetSDCardPath(0) + 'ext_card' + PathDelimedSubPath;
+        end;
+        if not DirectoryExists(Result) then
+        begin
+          Result := GetSDCardPath(0) + 'extsd' + PathDelimedSubPath;
+        end;
+        if not DirectoryExists(Result) then
+        begin
+          Result := GetSDCardPath(0) + '_ExternalSD' + PathDelimedSubPath;
+        end;
+        if not DirectoryExists(Result) then
+        begin
+          Result := GetSDCardPath(0) + 'external_sd' + PathDelimedSubPath;
+        end;
+        if not DirectoryExists(Result) then
+        begin
+          Result := GetSDCardPath(0) + 'removable_sdcard' + PathDelimedSubPath;
+        end;
+      end;
+
+      //last
+      if not DirectoryExists(Result) then
+      begin
+        Result := '/storage/sdcard' + IntToStr(Index) + PathDelimedSubPath;
+      end;
+    end
+    else if (Index >= UsbDiskStartIndex) and (Index < UsbDiskStartIndex + OTGDeivceCount) then  //UsbDiskStartIndex
+    begin
+      UsbIndex := Index - UsbDiskStartIndex;
+      TempVolumeIndex := UsbIndex;
+      Result := '';
+      for I := 1 to VolumePathList.Count - 1 do
+      begin
+        Result := ExcludeTrailingPathDelimiter(VolumePathList[I]);
+        if (Result.IndexOf('/storage/usb') < 0) and
+          (Result.IndexOf('/storage/otg') < 0) and
+          (Result.IndexOf('/mnt/otg') < 0) and
+          (Result.IndexOf('/mnt/usb') < 0) then
+        begin
+          Result := '';
+        end
+        else if DirectoryExists(Result) then
+        begin
+          if (Result.IndexOf('usbcd') >= 0) then
+          begin
+            Result := '';
+          end
+          else if (Result.IndexOf('otgcd') >= 0) then
+          begin
+            Result := '';
+          end
+          else if (TempVolumeIndex = 0) and (Result.Chars[Result.Length] in ['2'..'9']) then
+          begin
+            Result := '';
+          end
+          else if (TempVolumeIndex <> 0) and (Result.Chars[Result.Length] <> IntToStr(TempVolumeIndex)) then
+          begin
+            Result := '';
+          end
+          else
+          begin
+            Result := Result + PathDelimedSubPath;
+            break;
+          end;
+        end;
+      end;
+      if (Result = '') or (not DirectoryExists(Result)) then
+      begin
+        if UsbIndex = 0 then
+        begin
+          Result := '/storage/udisk' + PathDelimedSubPath;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/udisk' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'udisk' + PathDelimedSubPath;
+          end;
+
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/storage/usbdisk' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/usbdisk' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'usbdisk' + PathDelimedSubPath;
+          end;
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/storage/usbotg' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/usbotg' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'usbotg' + PathDelimedSubPath;
+          end;
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/storage/usbdrive' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/usbdrive' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'usbdrive' + PathDelimedSubPath;
+          end;
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/storage/usb' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/usb' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'usb' + PathDelimedSubPath;
+          end;
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/storage/otg' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/otg' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'otg' + PathDelimedSubPath;
+          end;
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := GetSDCardPath(0) + 'udisk' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := GetSDCardPath(0) + 'usbotg' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := GetSDCardPath(0) + 'usb' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := GetSDCardPath(0) + 'otg' + PathDelimedSubPath;
+          end;
+
+          //index = 0
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/storage/udisk0' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/udisk0' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'udisk0' + PathDelimedSubPath;
+          end;
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/storage/usbdisk0' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/usbdisk0' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'usbdisk0' + PathDelimedSubPath;
+          end;
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/storage/usbotg0' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/usbotg0' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'usbotg0' + PathDelimedSubPath;
+          end;
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/storage/usbdrive0' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/usbdrive0' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'usbdrive0' + PathDelimedSubPath;
+          end;
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/storage/usb0' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/usb0' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'usb0' + PathDelimedSubPath;
+          end;
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := GetSDCardPath(0) + 'udisk0' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := GetSDCardPath(0) + 'usbotg0' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := GetSDCardPath(0) + 'usb0' + PathDelimedSubPath;
+          end;
+        end
+        else
+        begin
+          //index > 0
+          IsFoundDir0 := False;
+          Result := GetSDCardPath(UsbDiskStartIndex);
+          IsFoundDir0 := Result.Chars[Result.Length] = '0';
+          if not IsFoundDir0 then
+          begin
+            inc(TempVolumeIndex);
+          end;
+          Result := '/storage/udisk' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/udisk' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'udisk' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/storage/usbdisk' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/usbdisk' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'usbdisk' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/storage/usbotg' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/usbotg' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'usbotg' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/storage/usbdrive' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/usbdrive' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'usbdrive' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/storage/usb' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/usb' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'usb' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/storage/otg' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/otg' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'otg' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := GetSDCardPath(0) + 'udisk' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := GetSDCardPath(0) + 'usbotg' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := GetSDCardPath(0) + 'usb' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := GetSDCardPath(0) + 'otg' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+        end;
+      end;
+
+      //last
+      if (Result = '') or (not DirectoryExists(Result)) then
+      begin
+        if UsbIndex = 0 then
+        begin
+          Result := '/storage/udisk' + PathDelimedSubPath;
+        end
+        else
+        begin
+          Result := '/storage/udisk' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+        end;
+      end;
+    end
+    else if (Index >= CDROMStartIndex) and (Index < CDROMStartIndex + OTGDeivceCount) then  //CDROMStartIndex
+    begin
+      CdRomIndex := Index - CDROMStartIndex;
+      TempVolumeIndex := CdRomIndex;
+      for I := 1 to VolumePathList.Count - 1 do
+      begin
+        Result := ExcludeTrailingPathDelimiter(VolumePathList[I]);
+        if (Result.IndexOf('/storage/cd') < 0) and
+          (Result.IndexOf('/mnt/cd') < 0) and
+          (Result.IndexOf('/storage/usbcd') < 0) and
+          (Result.IndexOf('/mnt/usbcd') < 0) then
+        begin
+          Result := '';
+        end
+        else if DirectoryExists(Result) then
+        begin
+          if (TempVolumeIndex = 0) and (Result.Chars[Result.Length] in ['2'..'9']) then
+          begin
+            Result := '';
+          end
+          else if (TempVolumeIndex <> 0) and (Result.Chars[Result.Length] <> IntToStr(TempVolumeIndex)) then
+          begin
+            Result := '';
+          end
+          else
+          begin
+            Result := Result + PathDelimedSubPath;
+            break;
+          end;
+        end;
+      end;
+      if (Result = '') or (not DirectoryExists(Result)) then
+      begin
+        if CdRomIndex = 0 then
+        begin
+          Result := '/storage/cd-rom' + PathDelimedSubPath;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/cd-rom' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'cd-rom' + PathDelimedSubPath;
+          end;
+
+          Result := '/storage/media_rw' + PathDelimedSubPath;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/media_rw' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'media_rw' + PathDelimedSubPath;
+          end;
+
+          Result := '/storage/cd_rom' + PathDelimedSubPath;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/cd_rom' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'cd_rom' + PathDelimedSubPath;
+          end;
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/storage/usbcdrom' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/usbcdrom' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'usbcdrom' + PathDelimedSubPath;
+          end;
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/storage/cdrom' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/cdrom' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'cdrom' + PathDelimedSubPath;
+          end;
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := GetSDCardPath(0) + 'cd-rom' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := GetSDCardPath(0) + 'usbcdrom' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := GetSDCardPath(0) + 'cdrom' + PathDelimedSubPath;
+          end;
+
+          Result := '/storage/cd-rom' + PathDelimedSubPath;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/cd-rom' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'cd-rom' + PathDelimedSubPath;
+          end;
+
+          //index = 0
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/storage/cd_rom0' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/cd_rom0' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'cd_rom0' + PathDelimedSubPath;
+          end;
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/storage/usbcdrom0' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/usbcdrom0' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'usbcdrom0' + PathDelimedSubPath;
+          end;
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/storage/cdrom0' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/cdrom0' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'cdrom0' + PathDelimedSubPath;
+          end;
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := GetSDCardPath(0) + 'cd-rom0' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := GetSDCardPath(0) + 'usbcdrom0' + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := GetSDCardPath(0) + 'cdrom0' + PathDelimedSubPath;
+          end;
+
+        end
+        else
+        begin
+          //index > 0
+          IsFoundDir0 := False;
+          Result := GetSDCardPath(CDROMStartIndex);
+          IsFoundDir0 := Result.Chars[Result.Length] = '0';
+          if not IsFoundDir0 then
+          begin
+            inc(TempVolumeIndex);
+          end;
+          Result := '/storage/cd_rom' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/cd_rom' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'cd_rom' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/storage/usbcdrom' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/usbcdrom' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'usbcdrom' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/storage/cdrom' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := '/mnt/cdrom' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := 'cdrom' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+
+          if not DirectoryExists(Result) then
+          begin
+            Result := GetSDCardPath(0) + 'cd-rom' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := GetSDCardPath(0) + 'usbcdrom' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+          if not DirectoryExists(Result) then
+          begin
+            Result := GetSDCardPath(0) + 'cdrom' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+          end;
+        end;
+      end;
+
+      //last
+      if (Result = '') or (not DirectoryExists(Result)) then
+      begin
+        if CdRomIndex = 0 then
+        begin
+          Result := '/storage/cd-rom' + PathDelimedSubPath;
+        end
+        else
+        begin
+          Result := '/storage/cd-rom' + IntToStr(TempVolumeIndex) + PathDelimedSubPath;
+        end;
+      end;
+    end
+    else
+    begin
+      Result := JNIgetExternalStorageDirectory(UnPathDelimedSubPath);
+    end;
+  finally
+    FreeAndNil(VolumePathList);
+  end;
+{$ELSE}
+  if UnPathDelimedSubPath <> '' then
+  begin
+    Result := IncludeTrailingPathDelimiter(
+      System.IOUtils.TPath.GetSharedDocumentsPath) + UnPathDelimedSubPath;
+  end
+  else
+  begin
+    Result := System.IOUtils.TPath.GetSharedDocumentsPath;
+  end;
+{$ENDIF ANDROID}
+  Result := IncludeTrailingPathDelimiter(Result);
+end;
+
+function GetAppPath: string;
+begin
+{$IF Defined(ANDROID)}
+  Result := ExtractFilePath(
+    ExcludeTrailingPathDelimiter(
+    System.IOUtils.TPath.GetHomePath));
+{$ELSE}
+  Result := ExtractFilePath(ParamStr(0));
+{$ENDIF ANDROID}
+  Result := IncludeTrailingPathDelimiter(Result);
+end;
+
+//initialization
+//
+//finalization
+//  UnRegisterSDReceiver;
+
+end.

+ 665 - 0
ONE.Objects.pas

@@ -0,0 +1,665 @@
+//------------------------------------------------------------------------------
+// 2016.07.19 Editor by Aone                                                   -
+// 2016.10.11 修正等比问题                                                     -
+//                                                                             -
+// 本控件修改自 Delphi Berlin 10.1 的 TSelection (FMX.Controls.pas)            -
+//                                                                             -
+// 修改重点:                                                                  -
+//  1. 移动点显示在上方                                                        -
+//  2. 增加(左中,上中,右中,下中)控制点,含原来的总共有 8 个控制点         -
+//                                                                             -
+// 代码说明:                                                                  -
+//  1. 代码内 {+++> 代表我增加的代码                                           -
+//  2. 代码内 {---> 代表我删除的代码                                           -
+//  3. 未来新版 Delphi 可以自己将 {+++> {---> 移植到新版代码内                 -
+//  4. 本控件不含 Register; 若需要请自行加入                                   -
+//------------------------------------------------------------------------------
+// http://www.cnblogs.com/onechen/                                             -
+//------------------------------------------------------------------------------
+
+unit ONE.Objects;
+
+interface
+
+uses
+  System.Classes, System.Types, System.UITypes, System.SysUtils,
+{+++>}System.Math,
+  System.UIConsts, System.Math.Vectors, FMX.Types, FMX.Graphics, FMX.Controls;
+
+type
+{ TOneSelection }
+
+  TOneSelection = class(TControl)
+  public const
+    DefaultColor = $FF1072C5;
+  public type
+    TGrabHandle = (None, LeftTop, RightTop, LeftBottom, RightBottom{+++>}, CenterLeft, CenterTop, CenterRight, CenterBottom{<+++});
+  private
+    FParentBounds: Boolean;
+    FOnChange: TNotifyEvent;
+    FHideSelection: Boolean;
+    FMinSize: Integer;
+    FOnTrack: TNotifyEvent;
+    FProportional: Boolean;
+    FGripSize: Single;
+    FRatio: Single;
+    FActiveHandle: TGrabHandle;
+    FHotHandle: TGrabHandle;
+    FDownPos: TPointF;
+    FShowHandles: Boolean;
+    FColor: TAlphaColor;
+    procedure SetHideSelection(const Value: Boolean);
+    procedure SetMinSize(const Value: Integer);
+    procedure SetGripSize(const Value: Single);
+    procedure ResetInSpace(const ARotationPoint: TPointF; ASize: TPointF);
+    function GetProportionalSize(const ASize: TPointF): TPointF;
+    function GetHandleForPoint(const P: TPointF): TGrabHandle;
+    procedure GetTransformLeftTop(AX, AY: Single; var NewSize: TPointF; var Pivot: TPointF);
+    procedure GetTransformLeftBottom(AX, AY: Single; var NewSize: TPointF; var Pivot: TPointF);
+    procedure GetTransformRightTop(AX, AY: Single; var NewSize: TPointF; var Pivot: TPointF);
+    procedure GetTransformRightBottom(AX, AY: Single; var NewSize: TPointF; var Pivot: TPointF);
+    procedure MoveHandle(AX, AY: Single);
+    procedure SetShowHandles(const Value: Boolean);
+    procedure SetColor(const Value: TAlphaColor);
+  protected
+    function DoGetUpdateRect: TRectF; override;
+{---> procedure Paint; override;
+{+++>}procedure AfterPaint; override;
+    ///<summary>Draw grip handle</summary>
+    procedure DrawHandle(const Canvas: TCanvas; const Handle: TGrabHandle; const Rect: TRectF); virtual;
+    ///<summary>Draw frame rectangle</summary>
+    procedure DrawFrame(const Canvas: TCanvas; const Rect: TRectF); virtual;
+  public
+    function PointInObjectLocal(X, Y: Single): Boolean; override;
+    constructor Create(AOwner: TComponent); override;
+    destructor Destroy; override;
+    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
+    procedure MouseMove(Shift: TShiftState; X, Y: Single); override;
+    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
+    procedure DoMouseLeave; override;
+    ///<summary>Grip handle where mouse is hovered</summary>
+    property HotHandle: TGrabHandle read FHotHandle;
+  published
+    property Align;
+    property Anchors;
+    property ClipChildren default False;
+    property ClipParent default False;
+    property Cursor default crDefault;
+    ///<summary>Selection frame and handle's border color</summary>
+    property Color: TAlphaColor read FColor write SetColor default DefaultColor;
+    property DragMode default TDragMode.dmManual;
+    property EnableDragHighlight default True;
+    property Enabled default True;
+    property GripSize: Single read FGripSize write SetGripSize;
+    property Locked default False;
+    property Height;
+    property HideSelection: Boolean read FHideSelection write SetHideSelection;
+    property HitTest default True;
+    property Padding;
+    property MinSize: Integer read FMinSize write SetMinSize default 15;
+    property Opacity;
+    property Margins;
+    property ParentBounds: Boolean read FParentBounds write FParentBounds default True;
+    property Proportional: Boolean read FProportional write FProportional;
+    property PopupMenu;
+    property Position;
+    property RotationAngle;
+    property RotationCenter;
+    property Scale;
+    property Size;
+    ///<summary>Indicates visibility of handles</summary>
+    property ShowHandles: Boolean read FShowHandles write SetShowHandles;
+    property Visible default True;
+    property Width;
+    property OnChange: TNotifyEvent read FOnChange write FOnChange;
+    {Drag and Drop events}
+    property OnDragEnter;
+    property OnDragLeave;
+    property OnDragOver;
+    property OnDragDrop;
+    property OnDragEnd;
+    {Mouse events}
+    property OnClick;
+    property OnDblClick;
+
+    property OnMouseDown;
+    property OnMouseMove;
+    property OnMouseUp;
+    property OnMouseWheel;
+    property OnMouseEnter;
+    property OnMouseLeave;
+
+    property OnPainting;
+    property OnPaint;
+    property OnResize;
+    property OnTrack: TNotifyEvent read FOnTrack write FOnTrack;
+  end;
+
+implementation
+
+{ TOneSelection }
+
+constructor TOneSelection.Create(AOwner: TComponent);
+begin
+  inherited;
+  AutoCapture := True;
+  ParentBounds := True;
+  FColor := DefaultColor;
+  FShowHandles := True;
+  FMinSize := 15;
+  FGripSize := 3;
+  SetAcceptsControls(False);
+end;
+
+destructor TOneSelection.Destroy;
+begin
+  inherited;
+end;
+
+function TOneSelection.GetProportionalSize(const ASize: TPointF): TPointF;
+begin
+  Result := ASize;
+{---> if FRatio * Result.Y  > Result.X  then
+{+++>}
+  if ((FRatio * Result.Y  > Result.X) and
+     not (FActiveHandle in [TGrabHandle.CenterTop, TGrabHandle.CenterBottom])) or
+         (FActiveHandle in [TGrabHandle.CenterLeft, TGrabHandle.CenterRight]) then
+{<+++}
+  begin
+    if Result.X < FMinSize then
+      Result.X := FMinSize;
+    Result.Y := Result.X / FRatio;
+    if Result.Y < FMinSize then
+    begin
+      Result.Y := FMinSize;
+      Result.X := FMinSize * FRatio;
+    end;
+  end
+  else
+  begin
+    if Result.Y < FMinSize then
+      Result.Y := FMinSize;
+    Result.X := Result.Y * FRatio;
+    if Result.X < FMinSize then
+    begin
+      Result.X := FMinSize;
+      Result.Y := FMinSize / FRatio;
+    end;
+  end;
+end;
+
+function TOneSelection.GetHandleForPoint(const P: TPointF): TGrabHandle;
+var
+{+++>}w, h: Single;
+  Local, R: TRectF;
+begin
+  Local := LocalRect;
+  R := TRectF.Create(Local.Left - GripSize, Local.Top - GripSize, Local.Left + GripSize, Local.Top + GripSize);
+  if R.Contains(P) then
+    Exit(TGrabHandle.LeftTop);
+  R := TRectF.Create(Local.Right - GripSize, Local.Top - GripSize, Local.Right + GripSize, Local.Top + GripSize);
+  if R.Contains(P) then
+    Exit(TGrabHandle.RightTop);
+  R := TRectF.Create(Local.Right - GripSize, Local.Bottom - GripSize, Local.Right + GripSize, Local.Bottom + GripSize);
+  if R.Contains(P) then
+    Exit(TGrabHandle.RightBottom);
+  R := TRectF.Create(Local.Left - GripSize, Local.Bottom - GripSize, Local.Left + GripSize, Local.Bottom + GripSize);
+  if R.Contains(P) then
+    Exit(TGrabHandle.LeftBottom);
+{+++>}
+  w := (Local.Right - Local.Left) / 2;
+  h := (Local.Bottom - Local.Top) / 2;
+  R := TRectF.Create(Local.Left - GripSize, (Local.Top + h) - GripSize, Local.Left + GripSize, (Local.Top + h) + GripSize);
+  if R.Contains(P) then
+    Exit(TGrabHandle.CenterLeft);
+  R := TRectF.Create((Local.Left + w) - GripSize, Local.Top - GripSize, (Local.Left + w) + GripSize, Local.Top + GripSize);
+  if R.Contains(P) then
+    Exit(TGrabHandle.CenterTop);
+  R := TRectF.Create(Local.Right - GripSize, (Local.Top + h) - GripSize, Local.Right + GripSize, (Local.Top + h) + GripSize);
+  if R.Contains(P) then
+    Exit(TGrabHandle.CenterRight);
+  R := TRectF.Create((Local.Left + w) - GripSize, Local.Bottom - GripSize, (Local.Left + w) + GripSize, Local.Bottom + GripSize);
+  if R.Contains(P) then
+    Exit(TGrabHandle.CenterBottom);
+{<+++}
+  Result := TGrabHandle.None;
+end;
+
+
+procedure TOneSelection.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
+begin
+  // this line may be necessary because TOneSelection is not a styled control;
+  // must further investigate for a better fix
+  if not Enabled then
+    Exit;
+
+  inherited;
+
+  FDownPos := TPointF.Create(X, Y);
+  if Button = TMouseButton.mbLeft then
+  begin
+    FRatio := Width / Height;
+    FActiveHandle := GetHandleForPoint(FDownPos);
+  end;
+end;
+
+procedure TOneSelection.MouseMove(Shift: TShiftState; X, Y: Single);
+var
+  P, OldPos: TPointF;
+  MoveVector: TVector;
+  MovePos: TPointF;
+  GrabHandle: TGrabHandle;
+begin
+  // this line may be necessary because TOneSelection is not a styled control;
+  // must further investigate for a better fix
+  if not Enabled then
+    Exit;
+
+  inherited;
+
+  MovePos := TPointF.Create(X, Y);
+  if not Pressed then
+  begin
+    // handle painting for hotspot mouse hovering
+    GrabHandle := GetHandleForPoint(MovePos);
+    if GrabHandle <> FHotHandle then
+      Repaint;
+    FHotHandle := GrabHandle;
+  end
+  else if ssLeft in Shift then
+  begin
+    if FActiveHandle = TGrabHandle.None then
+    begin
+      MoveVector := LocalToAbsoluteVector(TVector.Create(X - FDownPos.X, Y - FDownPos.Y));
+      if ParentControl <> nil then
+        MoveVector := ParentControl.AbsoluteToLocalVector(MoveVector);
+      Position.Point := Position.Point + TPointF(MoveVector);
+      if ParentBounds then
+      begin
+        if Position.X < 0 then
+          Position.X := 0;
+        if Position.Y < 0 then
+          Position.Y := 0;
+        if ParentControl <> nil then
+        begin
+          if Position.X + Width > ParentControl.Width then
+            Position.X := ParentControl.Width - Width;
+          if Position.Y + Height > ParentControl.Height then
+            Position.Y := ParentControl.Height - Height;
+        end
+        else
+          if Canvas <> nil then
+          begin
+            if Position.X + Width > Canvas.Width then
+              Position.X := Canvas.Width - Width;
+            if Position.Y + Height > Canvas.Height then
+              Position.Y := Canvas.Height - Height;
+          end;
+      end;
+      if Assigned(FOnTrack) then
+        FOnTrack(Self);
+      Exit;
+    end;
+
+    OldPos := Position.Point;
+    P := LocalToAbsolute(MovePos);
+    if ParentControl <> nil then
+      P := ParentControl.AbsoluteToLocal(P);
+    if ParentBounds then
+    begin
+      if P.Y < 0 then
+        P.Y := 0;
+      if P.X < 0 then
+        P.X := 0;
+      if ParentControl <> nil then
+      begin
+        if P.X > ParentControl.Width then
+          P.X := ParentControl.Width;
+        if P.Y > ParentControl.Height then
+          P.Y := ParentControl.Height;
+      end
+      else
+        if Canvas <> nil then
+        begin
+          if P.X > Canvas.Width then
+            P.X := Canvas.Width;
+          if P.Y > Canvas.Height then
+            P.Y := Canvas.Height;
+        end;
+    end;
+    MoveHandle(X, Y);
+  end;
+end;
+
+function TOneSelection.PointInObjectLocal(X, Y: Single): Boolean;
+begin
+  Result := inherited or (GetHandleForPoint(TPointF.Create(X, Y)) <> TGrabHandle.None);
+end;
+
+procedure TOneSelection.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
+begin
+  // this line may be necessary because TOneSelection is not a styled control;
+  // must further investigate for a better fix
+  if not Enabled then
+    Exit;
+
+  inherited;
+
+  if Assigned(FOnChange) then
+    FOnChange(Self);
+  FActiveHandle := TGrabHandle.None;
+end;
+
+procedure TOneSelection.DrawFrame(const Canvas: TCanvas; const Rect: TRectF);
+begin
+  Canvas.DrawDashRect(Rect, 0, 0, AllCorners, AbsoluteOpacity, FColor);
+end;
+
+procedure TOneSelection.DrawHandle(const Canvas: TCanvas; const Handle: TGrabHandle; const Rect: TRectF);
+var
+  Fill: TBrush;
+  Stroke: TStrokeBrush;
+begin
+  Fill := TBrush.Create(TBrushKind.Solid, claWhite);
+  Stroke := TStrokeBrush.Create(TBrushKind.Solid, FColor);
+  try
+    if Enabled then
+      if FHotHandle = Handle then
+        Canvas.Fill.Color := claRed
+      else
+        Canvas.Fill.Color := claWhite
+    else
+      Canvas.Fill.Color := claGrey;
+
+    Canvas.FillEllipse(Rect, AbsoluteOpacity, Fill);
+    Canvas.DrawEllipse(Rect, AbsoluteOpacity, Stroke);
+  finally
+    Fill.Free;
+    Stroke.Free;
+  end;
+end;
+
+{---> procedure TOneSelection.Paint;
+{+++>}procedure TOneSelection.AfterPaint;
+var
+{+++>}w, h: Single;
+  R: TRectF;
+begin
+  if FHideSelection then
+    Exit;
+
+  R := LocalRect;
+  R.Inflate(-0.5, -0.5);
+  DrawFrame(Canvas, R);
+
+  if ShowHandles then
+  begin
+    R := LocalRect;
+    DrawHandle(Canvas, TGrabHandle.LeftTop, TRectF.Create(R.Left - GripSize, R.Top - GripSize, R.Left + GripSize,
+      R.Top + GripSize));
+    DrawHandle(Canvas, TGrabHandle.RightTop, TRectF.Create(R.Right - GripSize, R.Top - GripSize, R.Right + GripSize,
+      R.Top + GripSize));
+    DrawHandle(Canvas, TGrabHandle.LeftBottom, TRectF.Create(R.Left - GripSize, R.Bottom - GripSize, R.Left + GripSize,
+      R.Bottom + GripSize));
+    DrawHandle(Canvas, TGrabHandle.RightBottom, TRectF.Create(R.Right - GripSize, R.Bottom - GripSize,
+      R.Right + GripSize, R.Bottom + GripSize));
+{+++>}
+    w := (R.Right - R.Left) / 2;
+    h := (R.Bottom - R.Top) / 2;
+    DrawHandle(Canvas, TGrabHandle.CenterLeft,   TRectF.Create( R.Left      - GripSize, (R.Top + h) - GripSize,  R.Left      + GripSize, (R.Top + h) + GripSize));
+    DrawHandle(Canvas, TGrabHandle.CenterTop,    TRectF.Create((R.Left + w) - GripSize,  R.Top      - GripSize, (R.Left + w) + GripSize,  R.Top      + GripSize));
+    DrawHandle(Canvas, TGrabHandle.CenterRight,  TRectF.Create( R.Right     - GripSize, (R.Top + h) - GripSize,  R.Right     + GripSize, (R.Top + h) + GripSize));
+    DrawHandle(Canvas, TGrabHandle.CenterBottom, TRectF.Create((R.Left + w) - GripSize,  R.Bottom   - GripSize, (R.Left + w) + GripSize,  R.Bottom   + GripSize));
+{<+++}
+  end;
+end;
+
+function TOneSelection.DoGetUpdateRect: TRectF;
+begin
+  Result := inherited;
+  Result.Inflate((FGripSize + 1) * Scale.X, (FGripSize + 1) * Scale.Y);
+end;
+
+
+procedure TOneSelection.ResetInSpace(const ARotationPoint: TPointF; ASize: TPointF);
+var
+  LLocalPos: TPointF;
+  LAbsPos: TPointF;
+begin
+  LAbsPos := LocalToAbsolute(ARotationPoint);
+  if ParentControl <> nil then
+  begin
+    LLocalPos := ParentControl.AbsoluteToLocal(LAbsPos);
+    LLocalPos.X := LLocalPos.X - ASize.X * RotationCenter.X * Scale.X;
+    LLocalPos.Y := LLocalPos.Y - ASize.Y * RotationCenter.Y * Scale.Y;
+    if ParentBounds then
+    begin
+      if LLocalPos.X < 0 then
+      begin
+        ASize.X := ASize.X + LLocalPos.X;
+        LLocalPos.X := 0;
+      end;
+      if LLocalPos.Y < 0 then
+      begin
+        ASize.Y := ASize.Y + LLocalPos.Y;
+        LLocalPos.Y := 0;
+      end;
+      if LLocalPos.X + ASize.X > ParentControl.Width then
+        ASize.X := ParentControl.Width - LLocalPos.X;
+      if LLocalPos.Y + ASize.Y > ParentControl.Height then
+        ASize.Y := ParentControl.Height - LLocalPos.Y;
+    end;
+  end
+  else
+  begin
+    LLocalPos.X := LAbsPos.X - ASize.X * RotationCenter.X * Scale.X;
+    LLocalPos.Y := LAbsPos.Y - ASize.Y * RotationCenter.Y * Scale.Y;
+  end;
+{+++>}if not FProportional or (FProportional and SameValue(ASize.X / ASize.Y, FRatio, 0.0001{SingleResolution})) then // 修正如果等比时,超界不会变形 by Aone @ 2016.10.11
+  SetBounds(LLocalPos.X, LLocalPos.Y, ASize.X, ASize.Y);
+end;
+
+procedure TOneSelection.GetTransformLeftTop(AX, AY: Single; var NewSize: TPointF; var Pivot: TPointF);
+var
+  LCorrect: TPointF;
+begin
+{+++>}
+  if FActiveHandle = TGrabHandle.CenterTop  then AX := 0 else
+  if FActiveHandle = TGrabHandle.CenterLeft then AY := 0;
+{+++>}
+  NewSize := Size.Size - TSizeF.Create(AX, AY);
+  if NewSize.Y < FMinSize then
+  begin
+    AY := Height - FMinSize;
+    NewSize.Y := FMinSize;
+  end;
+  if NewSize.X < FMinSize then
+  begin
+    AX := Width - FMinSize;
+    NewSize.X := FMinSize;
+  end;
+  if FProportional then
+  begin
+    LCorrect := NewSize;
+    NewSize := GetProportionalSize(NewSize);
+{+++>}if not (FActiveHandle in [TGrabHandle.CenterTop, TGrabHandle.CenterLeft]) then begin
+    LCorrect := LCorrect - NewSize;
+    AX := AX + LCorrect.X;
+    AY := AY + LCorrect.Y;
+{+++>}end;
+  end;
+  Pivot := TPointF.Create(Width * RotationCenter.X + AX * (1 - RotationCenter.X),
+    Height * RotationCenter.Y + AY * (1 - RotationCenter.Y));
+end;
+
+procedure TOneSelection.GetTransformLeftBottom(AX, AY: Single; var NewSize: TPointF; var Pivot: TPointF);
+var
+  LCorrect: TPointF;
+begin
+  {+++>}if FActiveHandle = TGrabHandle.CenterBottom then AX := 0;
+  NewSize := TPointF.Create(Width - AX, AY);
+  if NewSize.Y < FMinSize then
+  begin
+    AY := FMinSize;
+    NewSize.Y := FMinSize;
+  end;
+  if NewSize.X < FMinSize then
+  begin
+    AX := Width - FMinSize;
+    NewSize.X := FMinSize;
+  end;
+  if FProportional then
+  begin
+    LCorrect := NewSize;
+    NewSize := GetProportionalSize(NewSize);
+{+++>}if FActiveHandle <> TGrabHandle.CenterBottom then begin
+    LCorrect := LCorrect - NewSize;
+    AX := AX + LCorrect.X;
+{---> AY := AY + LCorrect.Y;
+{+++>}AY := AY - LCorrect.Y; // 修正等比缩放时,拉动左下角,右上角会跟着移动 by Aone @ 2016.10.10
+{+++>}end;
+  end;
+  Pivot := TPointF.Create(Width * RotationCenter.X + AX * (1 - RotationCenter.X),
+    Height * RotationCenter.Y + (AY - Height) * RotationCenter.Y);
+end;
+
+procedure TOneSelection.GetTransformRightTop(AX, AY: Single; var NewSize: TPointF; var Pivot: TPointF);
+var
+  LCorrect: TPointF;
+begin
+  {+++>}if FActiveHandle = TGrabHandle.CenterRight then AY := 0;
+  NewSize := TPointF.Create(AX, Height - AY);
+  if NewSize.Y < FMinSize then
+  begin
+    AY := Height - FMinSize;
+    NewSize.Y := FMinSize;
+  end;
+  if AX < FMinSize then
+  begin
+    AX := FMinSize;
+    NewSize.X := FMinSize;
+  end;
+  if FProportional then
+  begin
+    LCorrect := NewSize;
+    NewSize := GetProportionalSize(NewSize);
+{+++>}if FActiveHandle <> TGrabHandle.CenterRight then begin
+    LCorrect := LCorrect - NewSize;
+    AX := AX - LCorrect.X;
+    AY := AY + LCorrect.Y;
+{+++>}end;
+  end;
+  Pivot := TPointF.Create(Width * RotationCenter.X + (AX - Width) * RotationCenter.X,
+    Height * RotationCenter.Y + AY * (1 - RotationCenter.Y));
+end;
+
+procedure TOneSelection.GetTransformRightBottom(AX, AY: Single; var NewSize: TPointF; var Pivot: TPointF);
+var
+  LCorrect: TPointF;
+begin
+  NewSize := TPointF.Create(AX, AY);
+  if NewSize.Y < FMinSize then
+  begin
+    AY := FMinSize;
+    NewSize.Y := FMinSize;
+  end;
+  if NewSize.X < FMinSize then
+  begin
+    AX := FMinSize;
+    NewSize.X := FMinSize;
+  end;
+  if FProportional then
+  begin
+    LCorrect := NewSize;
+    NewSize := GetProportionalSize(NewSize);
+    LCorrect := LCorrect - NewSize;
+    AX := AX - LCorrect.X;
+    AY := AY - LCorrect.Y;
+  end;
+  Pivot := TPointF.Create(Width * RotationCenter.X + (AX - Width) * RotationCenter.X,
+    Height * RotationCenter.Y + (AY - Height) * RotationCenter.Y);
+end;
+
+procedure TOneSelection.MoveHandle(AX, AY: Single);
+var
+  NewSize, Pivot: TPointF;
+begin
+  case FActiveHandle of
+    TOneSelection.TGrabHandle.LeftTop: GetTransformLeftTop(AX, AY, NewSize, Pivot);
+    TOneSelection.TGrabHandle.LeftBottom: GetTransformLeftBottom(AX, AY, NewSize, Pivot);
+    TOneSelection.TGrabHandle.RightTop: GetTransformRightTop(AX, AY, NewSize, Pivot);
+    TOneSelection.TGrabHandle.RightBottom: GetTransformRightBottom(AX, AY, NewSize, Pivot);
+{+++>}
+    TOneSelection.TGrabHandle.CenterLeft: GetTransformLeftTop(AX, AY, NewSize, Pivot);
+    TOneSelection.TGrabHandle.CenterTop: GetTransformLeftTop(AX, AY, NewSize, Pivot);
+    TOneSelection.TGrabHandle.CenterRight: GetTransformRightTop(AX, AY, NewSize, Pivot);
+    TOneSelection.TGrabHandle.CenterBottom: GetTransformLeftBottom(AX, AY, NewSize, Pivot);
+{<+++}
+  end;
+  ResetInSpace(Pivot, NewSize);
+  if Assigned(FOnTrack) then
+    FOnTrack(Self);
+end;
+
+procedure TOneSelection.DoMouseLeave;
+begin
+  inherited;
+  FHotHandle := TGrabHandle.None;
+  Repaint;
+end;
+
+procedure TOneSelection.SetHideSelection(const Value: Boolean);
+begin
+  if FHideSelection <> Value then
+  begin
+    FHideSelection := Value;
+    Repaint;
+  end;
+end;
+
+procedure TOneSelection.SetMinSize(const Value: Integer);
+begin
+  if FMinSize <> Value then
+  begin
+    FMinSize := Value;
+    if FMinSize < 1 then
+      FMinSize := 1;
+  end;
+end;
+
+procedure TOneSelection.SetShowHandles(const Value: Boolean);
+begin
+  if FShowHandles <> Value then
+  begin
+    FShowHandles := Value;
+    Repaint;
+  end;
+end;
+
+procedure TOneSelection.SetColor(const Value: TAlphaColor);
+begin
+  if FColor <> Value then
+  begin
+    FColor := Value;
+    Repaint;
+  end;
+end;
+
+procedure TOneSelection.SetGripSize(const Value: Single);
+begin
+  if FGripSize <> Value then
+  begin
+    if Value < FGripSize then
+      Repaint;
+    FGripSize := Value;
+    if FGripSize > 20 then
+      FGripSize := 20;
+    if FGripSize < 1 then
+      FGripSize := 1;
+    HandleSizeChanged;
+    Repaint;
+  end;
+end;
+
+end.
+

+ 290 - 0
UMeng.pas

@@ -0,0 +1,290 @@
+unit UMeng;
+
+//https://developer.umeng.com/docs/66632/detail/66889
+//delphi 不支持Android 64,所以不能引用64位 so:arm64-v8a
+
+interface
+
+uses
+  System.Sysutils, System.Generics.Collections;
+
+type
+  TUMeng = class
+  private
+    class var FLastViewName: string;
+    class function DeviceType(IsPhone: Boolean): Integer;
+    class procedure Debug;
+  public
+    //注意: 即使您已经在AndroidManifest.xml中配置过appkey和channel值,
+    //也需要在App代码中调用初始化接口(如需要使用AndroidManifest.xml中配置好的appkey和channel值,
+    //UMConfigure.init调用中appkey和channel参数请置为null)。
+    class procedure Init(appkey, channel: string; pushSecret: string = ''; IsPhone: Boolean = True); overload;
+    //注意:如果您已经在AndroidManifest.xml中配置过appkey和channel值,可以调用此版本初始化函数。
+    class procedure Init(pushSecret: string = ''; IsPhone: Boolean = True); overload;
+    class procedure onResume;
+    class procedure onPause;
+    class procedure onPageStart(ViewName: string);
+    class procedure onPageEnd(ViewName: string);
+    class procedure onPageAutoChange(ViewName: string = ''; NeedPauseAndResume: Boolean = False);
+    //普通事件
+    class procedure onEvent(AID: string; ALabel: string = #0); overload;
+    //多属性(K-V)事件
+    class procedure onEvent(AID: string; AProperties: TArray<TPair<string, string>>); overload;
+    //数值型
+    class procedure onEventValue(AID: string; AProperties: TArray<TPair<string, string>>; ADuration: Integer = 0);
+    //上次访问的页面名
+    class property LastViewName: string read FLastViewName;
+  end;
+
+implementation
+
+uses
+{$IFDEF ANDROID}
+  Androidapi.JNI.Os, Androidapi.JNI.Provider,
+  Androidapi.JNIBridge, Androidapi.Helpers,
+  Androidapi.JNI.JavaTypes, Androidapi.JNI.App,
+  Androidapi.JNI.GraphicsContentViewText,
+  Androidapi.JNI.umeng.common, Androidapi.JNI.umeng.analytics,
+{$ENDIF}
+  FMX.Types;
+
+{ TUMeng }
+
+class procedure TUMeng.Debug;
+{$IF DEFINED(ANDROID) && DEFINED(DEBUG)}
+var
+  I: Integer;
+  sDeviceInfo: string;
+  arrInfo: TJavaObjectArray<JString>;
+{$ENDIF}
+begin
+{$IF DEFINED(ANDROID) && DEFINED(DEBUG)}
+  (**
+  * 设置组件化的Log开关
+  * 参数: boolean 默认为false,如需查看LOG设置为true
+  *)
+  TJUMConfigure.JavaClass.setLogEnabled(True);
+  TJMobclickAgent.JavaClass.setDebugMode(True);
+
+  sDeviceInfo := '';
+  arrInfo := TJUMConfigure.JavaClass.getTestDeviceInfo(
+    {$IF CompilerVersion > 27}
+    TAndroidHelper.Context
+    {$ELSE}
+    SharedActivityContext
+    {$ENDIF}
+  );
+
+  for I := 0 to arrInfo.Length - 1 do
+    sDeviceInfo := Format('%s %s', [sDeviceInfo, JStringToString(arrInfo.Items[I])]);
+
+  Log.d('TUMeng-DeviceInfo:' + sDeviceInfo);
+
+  //setCatchUncaughtExceptions can not be called in child process
+  //TJMobclickAgent.JavaClass.setCatchUncaughtExceptions(True);
+{$ENDIF}
+end;
+
+class procedure TUMeng.onEvent(AID: string; ALabel: string = #0);
+begin
+{$IFDEF ANDROID}
+  TJMobclickAgent.JavaClass.onEvent(
+    {$IF CompilerVersion > 27}
+    TAndroidHelper.Context
+    {$ELSE}
+    SharedActivityContext
+    {$ENDIF},
+    StringToJString(AID),
+    StringToJString(ALabel)
+  );
+{$ENDIF}
+end;
+
+class function TUMeng.DeviceType(IsPhone: Boolean): Integer;
+begin
+{$IFDEF ANDROID}
+  if IsPhone then
+    Result := TJUMConfigure.JavaClass.DEVICE_TYPE_PHONE
+  else
+    Result := TJUMConfigure.JavaClass.DEVICE_TYPE_BOX;
+{$ENDIF}
+end;
+
+(**
+* 初始化common库
+* 参数1:上下文,不能为空
+* 参数2:设备类型,UMConfigure.DEVICE_TYPE_PHONE为手机、UMConfigure.DEVICE_TYPE_BOX为盒子,默认为手机
+* 参数3:Push推送业务的secret
+*)
+class procedure TUMeng.Init(pushSecret: string; IsPhone: Boolean);
+begin
+{$IFDEF DEBUG}
+  Debug;
+{$ENDIF}
+
+  FLastViewName := '';
+
+{$IFDEF ANDROID}
+  TJUMConfigure.TU2Fixinit(
+    {$IF CompilerVersion > 27}
+    TAndroidHelper.Context
+    {$ELSE}
+    SharedActivityContext
+    {$ENDIF},
+    DeviceType(IsPhone),
+    StringToJString(pushSecret));
+{$ENDIF}
+end;
+
+(**
+* 初始化common库
+* 参数1:上下文,不能为空
+* 参数2:【友盟+】 AppKey
+* 参数3:【友盟+】 Channel
+* 参数4:设备类型,UMConfigure.DEVICE_TYPE_PHONE为手机、UMConfigure.DEVICE_TYPE_BOX为盒子,默认为手机
+* 参数5:Push推送业务的secret
+*)
+class procedure TUMeng.Init(appkey, channel, pushSecret: string;
+  IsPhone: Boolean);
+begin
+{$IFDEF DEBUG}
+  Debug;
+{$ENDIF}
+
+  FLastViewName := '';
+
+{$IFDEF ANDROID}
+  TJUMConfigure.TU2Fixinit(
+    {$IF CompilerVersion > 27}
+    TAndroidHelper.Context
+    {$ELSE}
+    SharedActivityContext
+    {$ENDIF},
+    StringToJString(appkey),
+    StringToJString(channel),
+    DeviceType(IsPhone),
+    StringToJString(pushSecret));
+
+  // 页面采集模式
+  TJMobclickAgent.JavaClass.setPageCollectionMode(TJMobclickAgent_PageMode.JavaClass.AUTO);
+{$ENDIF}
+end;
+
+class procedure TUMeng.onEvent(AID: string;
+  AProperties: TArray<TPair<string, string>>);
+begin
+  onEventValue(AID, AProperties);
+end;
+
+class procedure TUMeng.onEventValue(AID: string;
+  AProperties: TArray<TPair<string, string>>; ADuration: Integer);
+{$IFDEF ANDROID}
+var
+  FJMap: JHashMap;
+  I: Integer;
+  LProperty: TPair<string, string>;
+{$ENDIF}
+begin
+{$IFDEF ANDROID}
+  FJMap := TJHashMap.JavaClass.init;
+  for I := 0 to Length(AProperties) - 1 do
+    FJMap.put(StringToJString(AProperties[I].Key), StringToJString(AProperties[I].Value));
+
+  if ADuration > 0 then
+    TJMobclickAgent.JavaClass.onEventValue(
+      {$IF CompilerVersion > 27}
+      TAndroidHelper.Context
+      {$ELSE}
+      SharedActivityContext
+      {$ENDIF},
+      StringToJString(AID),
+      Jmap(FJMap),
+      ADuration
+    )
+  else
+    TJMobclickAgent.JavaClass.onEvent(
+      {$IF CompilerVersion > 27}
+      TAndroidHelper.Context
+      {$ELSE}
+      SharedActivityContext
+      {$ENDIF},
+      StringToJString(AID),
+      Jmap(FJMap)
+    );
+{$ENDIF}
+end;
+
+class procedure TUMeng.onPageAutoChange(ViewName: string; NeedPauseAndResume: Boolean);
+begin
+  {$IFDEF DEBUG}
+  Log.d('---onPageAutoChange:' + FLastViewName + '/' + ViewName);
+  {$ENDIF}
+
+  if FLastViewName <> '' then begin
+    onPageEnd(FLastViewName);
+    if NeedPauseAndResume then
+      onPause;
+  end;
+
+  if ViewName <> '' then begin
+    onPageStart(ViewName);
+    if NeedPauseAndResume then
+      onResume;
+  end;
+
+  FLastViewName := ViewName;
+end;
+
+class procedure TUMeng.onPageEnd(ViewName: string);
+begin
+{$IFDEF DEBUG}
+  Log.d('---onPageEnd:' + ViewName);
+{$ENDIF}
+{$IFDEF ANDROID}
+  TJMobclickAgent.JavaClass.onPageEnd(StringToJString(ViewName));
+{$ENDIF}
+end;
+
+class procedure TUMeng.onPageStart(ViewName: string);
+begin
+{$IFDEF DEBUG}
+  Log.d('---onPageStart:' + ViewName);
+{$ENDIF}
+{$IFDEF ANDROID}
+  TJMobclickAgent.JavaClass.onPageStart(StringToJString(ViewName));
+{$ENDIF}
+end;
+
+class procedure TUMeng.onPause;
+begin
+{$IFDEF DEBUG}
+  Log.d('---onPause');
+{$ENDIF}
+{$IFDEF ANDROID}
+  TJMobclickAgent.JavaClass.onPause(
+    {$IF CompilerVersion > 27}
+    TAndroidHelper.Context
+    {$ELSE}
+    SharedActivityContext
+    {$ENDIF}
+  );
+{$ENDIF}
+end;
+
+class procedure TUMeng.onResume;
+begin
+{$IFDEF DEBUG}
+  Log.d('---onResume');
+{$ENDIF}
+{$IFDEF ANDROID}
+  TJMobclickAgent.JavaClass.onResume(
+    {$IF CompilerVersion > 27}
+    TAndroidHelper.Context
+    {$ELSE}
+    SharedActivityContext
+    {$ENDIF}
+  );
+{$ENDIF}
+end;
+
+end.

+ 1605 - 0
ksBitmapHelper.pas

@@ -0,0 +1,1605 @@
+unit ksBitmapHelper;
+
+// 非安卓部分拷贝自 FlyUtils.TBitmapHelper
+
+{$DEFINE FMX}
+
+interface
+
+uses
+{$IFDEF FMX}
+  FMX.Graphics,
+  FMX.Utils,
+  FMX.Types,
+  FMX.Surfaces,
+{$ELSE}
+  Vcl.Graphics,
+{$ENDIF}
+  System.Types,
+  System.UITypes,
+  System.Classes;
+
+{$IFDEF FMX}
+{$ELSE}
+resourcestring
+  SBitmapSavingFailed = 'Saving bitmap failed.';
+  SBitmapSavingFailedNamed = 'Saving bitmap failed (%s).';
+{$ENDIF}
+
+var
+  MonochromeChange_Threshold: Byte = 120;
+  MonochromeChange_Weighting_Red: Double = 0.3;
+  MonochromeChange_Weighting_Green: Double = 0.59;
+  MonochromeChange_Weighting_Blue: Double = 0.11;
+
+type
+  /// <summary>
+  ///   转黑白的方法
+  /// </summary>
+  TMonochromeChangeType = (
+    /// <summary>
+    ///   平均值
+    /// </summary>
+    Average,
+    /// <summary>
+    ///   权值
+    /// </summary>
+    Weighting,
+    /// <summary>
+    ///   最大值
+    /// </summary>
+    Max);
+
+  /// <summary>
+  ///   <para>
+  ///     TBitmap Save As BMP
+  ///   </para>
+  ///   <para>
+  ///     BytesPerPixel = -1 表示自动
+  ///   </para>
+  /// </summary>
+  TKngStrBitmapHelper = class helper for TBitmap
+  public
+    function SaveAsBMPToFile(const AFileName: string; const BytesPerPixel: Integer = 3;
+      const MonochromeChangeType: TMonochromeChangeType = TMonochromeChangeType.Average): Boolean; overload;
+    procedure SaveAsBMPToFileDef(const AFileName: string); overload;
+    function SaveAsBMPToStream(const AStream: TStream; const BytesPerPixel: Integer = 3;
+      const MonochromeChangeType: TMonochromeChangeType = TMonochromeChangeType.Average): Boolean; overload;
+    procedure SaveAsBMPToStreamDef(Stream: TStream); overload;
+{$IFDEF FMX}
+    /// <summary>
+    /// 用于在线程中代替 LoadFromFile ,不用自己调用 Synchronize 了。
+    /// </summary>
+    procedure SyncLoadFromFile(const AFileName: string);
+    /// <summary>
+    /// 用于在线程中代替 LoadThumbnailFromFile ,不用自己调用 Synchronize 了。
+    /// </summary>
+    procedure SyncLoadThumbnailFromFile(const AFileName: string; const AFitWidth, AFitHeight: Single;
+      const UseEmbedded: Boolean = True);
+    /// <summary>
+    /// 用于在线程中代替 LoadFromStream ,不用自己调用 Synchronize 了。
+    /// </summary>
+    procedure SyncLoadFromStream(Stream: TStream);
+    /// <summary>
+    /// 用于在线程中代替 LoadThumbnailFromStream ,不用自己调用 Synchronize 了。
+    /// </summary>
+    procedure SyncLoadThumbnailFromStream(Stream: TStream; const AFitWidth, AFitHeight: Single;
+      const UseEmbedded: Boolean = True; const AutoCut: Boolean = True);
+    /// <summary>
+    /// 用于在线程中代替 Assign ,不用自己调用 Synchronize 了。
+    /// </summary>
+    procedure SyncAssign(Source: TPersistent);
+{$ENDIF}
+  end;
+
+{$IFDEF FMX}
+  TBitmapCodecDescriptorField = (Extension, Description);
+  TKngStrBitmapCodecManager = class helper for TBitmapCodecManager
+  strict private
+    class function GuessCodecClass(const Name: string; const Field: TBitmapCodecDescriptorField): TCustomBitmapCodecClass;
+  public
+    class function GetImageSize(const AFileName: string): TPointF; overload;
+    class function GetImageSize(const AStream: TStream): TPointF; overload;
+    class function LoadFromStream(const AStream: TStream; const Bitmap: TBitmapSurface;
+      const MaxSizeLimit: Cardinal = 0): Boolean;
+    class function LoadThumbnailFromStream(const AStream: TStream; const AFitWidth, AFitHeight: Single;
+      const UseEmbedded: Boolean; const AutoCut: Boolean; const Bitmap: TBitmapSurface): Boolean;
+  end;
+{$ENDIF}
+
+{$IFDEF FMX}
+{$ELSE}
+procedure GraphicToBitmap(const Src: Vcl.Graphics.TGraphic;
+  const Dest: Vcl.Graphics.TBitmap; const TransparentColor: Vcl.Graphics.TColor = Vcl.Graphics.clNone);
+{$ENDIF}
+
+const
+  SizeOftagBITMAPFILEHEADER = 14;
+  SizeOftagBITMAPINFOHEADER = 40;
+  RGB565ExtDataLen  = 12;
+
+{$IFDEF FMX}
+
+function FillScanLineFormMemory(BitmapData: TBitmapData; ScanLineIndex, Width: integer;
+      InputData: Pointer; InputFormat: TPixelFormat): Boolean;
+
+type
+  // copy form [广州]庾伟洪<ywthegod@qq.com>
+  TBitmap16bitFiler = class
+  class var
+    Color: array [0 .. $FFFF] of TAlphaColor;
+    class constructor Create;
+    class procedure FillScanLine(D: TBitmapData; ScanLine, Width: integer;
+      data: Pointer); inline;
+  end;
+
+//  PAlphaColorArray = ^TAlphaColorArray;  //FMX.Utils,
+//  TWordArray = array [0 .. 0] of Word;
+//  PWordArray = ^TWordArray;              //System.SysUtils
+
+//  y 行号 h 高度 w 宽度 b 一个像素用字节数
+// FVideoBuf 数据内存
+//        bm.Map(TMapAccess.Write, bd);
+//        try
+//          for y := 0 to h - 1 do
+//          begin
+//            TBitmap16bitFiler.FillScanLine(bd, y, w, Addr(FVideoBuf[y * w * b]));
+//          end;
+//        finally
+//          bm.Unmap(bd);
+//        end;
+{$ENDIF}
+
+implementation
+
+uses
+{$IFDEF FMX}
+  FMX.Consts,
+  {$IFDEF ANDROID}
+  FMX.Graphics.Android,
+  Androidapi.JNIBridge,
+  Androidapi.Helpers,
+  FMX.Helpers.Android,
+  Androidapi.JNI.GraphicsContentViewText,
+  {$ENDIF}
+  {$IFDEF MSWINDOWS}
+  FMX.Canvas.D2D, Winapi.Wincodec,  Winapi.Windows,
+  {$ENDIF}
+{$ELSE}
+  Vcl.Consts,
+{$ENDIF}
+  System.SysConst,
+  System.SysUtils,
+  System.Math;
+
+
+//add by 爱吃猪头肉。
+type
+//感谢 yu  273637089 的测试和 提供 packed 信息。
+{$IFDEF FMX}
+{$ELSE}
+  tagRGBTRIPLE = packed record
+    rgbtBlue: Byte;
+    rgbtGreen: Byte;
+    rgbtRed: Byte;
+  end;
+  PRGBTripleArray = ^TRGBTripleArray;
+  TRGBTripleArray = array [Byte] of tagRGBTRIPLE;
+{$ENDIF}
+
+  tagBITMAPFILEHEADER = packed record
+    bfType: Word;
+    bfSize: DWORD;
+    bfReserved1: Word;
+    bfReserved2: Word;
+    bfOffBits: DWORD;
+  end;
+
+  tagBITMAPINFOHEADER = packed record
+    biSize: DWORD;
+    biWidth: Longint;
+    biHeight: Longint;
+    biPlanes: Word;
+    biBitCount: Word;
+    biCompression: DWORD;
+    biSizeImage: DWORD;
+    biXPelsPerMeter: Longint;
+    biYPelsPerMeter: Longint;
+    biClrUsed: DWORD;
+    biClrImportant: DWORD;
+  end;
+
+  tagRGBQUAD = packed record
+    rgbBlue: Byte;
+    rgbGreen: Byte;
+    rgbRed: Byte;
+    rgbReserved: Byte;
+  end;
+
+{$IFDEF FMX}
+  {$IFDEF ANDROID}
+  TBitmapCodecAndroid = class(FMX.Graphics.Android.TBitmapCodecAndroid)
+  private
+    class function IsGIFStream(const Stream: TStream): Boolean;
+    function LoadMovieFromStreamScaled(const AStream: TStream;
+      const Surface: TBitmapSurface; const FitSize: TPoint): Boolean;
+    function LoadMovieFromStream(const Stream: TStream;
+      const Surface: TBitmapSurface): Boolean;
+    function StretchIfNeed(const SrcBitmap: JBitmap;
+      const Bitmap: TBitmapSurface; const LoadOptions: JBitmapFactory_Options;
+      const MaxSizeLimit: Cardinal): Boolean;
+    class function GetMovieSize(const Stream: TStream): TPoint;
+  public
+    class function GetImageSize(const AFileName: string): TPointF; overload;
+    class function GetImageSize(const AStream: TStream): TPointF; overload;
+    function LoadFromStream(const AStream: TStream; const Bitmap: TBitmapSurface;
+      const MaxSizeLimit: Cardinal): Boolean; override;
+    function LoadThumbnailFromStream(const AStream: TStream; const AFitWidth, AFitHeight: Single;
+      const UseEmbedded: Boolean; const AutoCut: Boolean; const Bitmap: TBitmapSurface): Boolean;
+  end;
+  {$ENDIF}
+  {$IFDEF MSWINDOWS}
+  TBitmapCodecWIC = class(FMX.Canvas.D2D.TCustomBitmapCodecWIC)
+  private
+    function DecodeFrame(const Frame: IWICBitmapFrameDecode; const Bitmap: TBitmapSurface;
+      const MaxSizeLimit: Cardinal = 0): Boolean;
+  public
+    class function GetImageSize(const AStream: TStream): TPointF; overload;
+    function LoadThumbnailFromStream(const AStream: TStream; const AFitWidth, AFitHeight: Single;
+      const UseEmbedded: Boolean; const AutoCut: Boolean; const Bitmap: TBitmapSurface): Boolean;
+  end;
+  {$ENDIF}
+{$ENDIF}
+
+const
+  { constants for the biCompression field }
+  {$EXTERNALSYM BI_RGB}
+  BI_RGB = 0;
+  {$EXTERNALSYM BI_RLE8}
+  BI_RLE8 = 1;
+  {$EXTERNALSYM BI_RLE4}
+  BI_RLE4 = 2;
+  {$EXTERNALSYM BI_BITFIELDS}
+  BI_BITFIELDS = 3;
+
+const
+  RGB565_MASK_RED   = $F800;
+  RGB565_MASK_GREEN = $07E0;
+  RGB565_MASK_BLUE  = $001F;
+//  RGB565ExtDataLen  = 12;
+
+function rgb_24_2_565(r,g,b: Byte): UInt16;
+begin
+//  r := r * 31 div 255;
+//  g := g * 64 div 255;
+//  b := b * 31 div 255;
+//  Result := r *2048 or g *32 or b;
+//  Result := ((r shl 8) and RGB565_MASK_RED) or ((g shl 3) and RGB565_MASK_GREEN) or (b shr 3);
+  Result := ((r shr 3) shl 11) or ((g shr 2 ) shl 5) or (b shr 3);
+end;
+{
+    return (USHORT)(((unsigned(r) << 8) & 0xF800) |
+            ((unsigned(g) << 3) & 0x7E0)  |
+            ((unsigned(b) >> 3)));
+}
+
+procedure rgb565_2_rgb24(rgb24: TBytes; rgb565: UInt16);
+begin
+ //extract RGB
+ rgb24[2] := (rgb565 and RGB565_MASK_RED) shr 11;
+ rgb24[1] := (rgb565 and RGB565_MASK_GREEN) shr 5;
+ rgb24[0] := (rgb565 and RGB565_MASK_BLUE);
+
+ //amplify the image
+ rgb24[2] := rgb24[2] shl 3;
+ rgb24[1] := rgb24[2] shl 2;
+ rgb24[0] := rgb24[2] shl 3;
+end;
+{
+ //extract RGB
+ rgb24[2] = (rgb565 & RGB565_MASK_RED) >> 11;
+ rgb24[1] = (rgb565 & RGB565_MASK_GREEN) >> 5;
+ rgb24[0] = (rgb565 & RGB565_MASK_BLUE);
+
+ //amplify the image
+ rgb24[2] <<= 3;
+ rgb24[1] <<= 2;
+ rgb24[0] <<= 3;
+}
+
+const
+  RGB555_MASK_RED   = $7C00;
+  RGB555_MASK_GREEN = $03E0;
+  RGB555_MASK_BLUE  = $001F;
+
+function rgb_24_2_555(r,g,b: Byte): UInt16;
+begin
+  Result := ((r shl 7) and RGB555_MASK_RED) or ((g shl 2) and RGB555_MASK_GREEN) or (b shr 3);
+end;
+
+procedure rgb555_2_rgb24(rgb24: TBytes; rgb555: UInt16);
+begin
+ //extract RGB
+ rgb24[0] := (rgb555 shl 3) and $00F8;
+ rgb24[1] := (rgb555 shr 2) and $00F8;
+ rgb24[2] := (rgb555 shr 7) and $00F8;
+end;
+
+{$IFDEF FMX}
+{$ELSE}
+procedure GraphicToBitmap(const Src: Vcl.Graphics.TGraphic;
+  const Dest: Vcl.Graphics.TBitmap; const TransparentColor: Vcl.Graphics.TColor = Vcl.Graphics.clNone);
+begin
+  // Do nothing if either source or destination are nil
+  if not Assigned(Src) or not Assigned(Dest) then
+    Exit;
+  if Src.Empty then exit;
+
+  // Size the bitmap
+  Dest.Width := Src.Width;
+  Dest.Height := Src.Height;
+  if Src.Transparent then
+  begin
+    // Source graphic is transparent, make bitmap behave transparently
+    Dest.Transparent := True;
+    if (TransparentColor <> Vcl.Graphics.clNone) then
+    begin
+      // Set destination as transparent using required colour key
+      Dest.TransparentColor := TransparentColor;
+      Dest.TransparentMode := Vcl.Graphics.tmFixed;
+      // Set background colour of bitmap to transparent colour
+      Dest.Canvas.Brush.Color := TransparentColor;
+    end
+    else
+      // No transparent colour: set transparency to automatic
+      Dest.TransparentMode := Vcl.Graphics.tmAuto;
+  end;
+  // Clear bitmap to required background colour and draw bitmap
+  Dest.Canvas.FillRect(System.Classes.Rect(0, 0, Dest.Width, Dest.Height));
+  Dest.Canvas.Draw(0, 0, Src);
+end;
+{$ENDIF}
+//该代码片段来自于: http://www.sharejs.com/codes/delphi/2248
+
+
+{ TKngStrBitmapHelper }
+
+function TKngStrBitmapHelper.SaveAsBMPToFile(const AFileName: string; const BytesPerPixel: Integer = 3;
+  const MonochromeChangeType: TMonochromeChangeType = TMonochromeChangeType.Average): Boolean;
+var
+  AStream: TStream;
+begin
+  Result := False;
+{$IFDEF FMX}
+  if IsEmpty then exit;
+{$ELSE}
+  if Empty then exit;
+{$ENDIF}
+  AStream := TFileStream.Create(AFileName, fmCreate);// or fmOpenReadWrite);
+  try
+    Result := SaveAsBMPToStream(AStream, BytesPerPixel);
+//    if Result then
+//      AStream.Size := AStream.Position;
+  finally
+    FreeAndNil(AStream);
+  end;
+end;
+
+procedure TKngStrBitmapHelper.SaveAsBMPToFileDef(const AFileName: string);
+begin
+  if not SaveAsBMPToFile(AFileName) then
+  begin
+{$IFDEF FMX}
+    raise EBitmapSavingFailed.CreateFMT(SBitmapSavingFailed, [AFileName]);
+{$ELSE}
+    raise EInvalidGraphicOperation.CreateFmt(SBitmapSavingFailed, [AFileName]);
+{$ENDIF}
+  end;
+end;
+
+//http://blog.csdn.net/pjpsmile/article/details/8985523
+function TKngStrBitmapHelper.SaveAsBMPToStream(const AStream: TStream; const BytesPerPixel: Integer = 3;
+  const MonochromeChangeType: TMonochromeChangeType = TMonochromeChangeType.Average): Boolean;
+var
+  I, CurrBytesPerPixel,
+  wWidth, nCol, wRow, wByteIdex,
+  bfReserved1, bfReserved2,
+  nBmpWidth, nBmpHeight, bufferSize: Integer;
+  BitmapInfo: tagBITMAPINFOHEADER;
+  BMF: tagBITMAPFILEHEADER;
+{$IFDEF FMX}
+  Data: TBitmapData;
+  clr: TAlphaColor;
+  AlphaColorBuffer: PAlphaColor;
+{$ELSE}
+  Pixs : pRGBTripleArray;
+{$ENDIF}
+  bmpData: TBytes;
+  A32BitData: UInt32;
+  ABitIndex,
+  A8BitData: Byte;
+  RGBQUAD: tagRGBQUAD;
+//  FileSizeFix,
+  A16BitData,
+  GrayeData: UInt16;
+begin
+  Result := False;
+{$IFDEF FMX}
+  if IsEmpty then exit;
+{$ELSE}
+  if Empty then exit;
+{$ENDIF}
+  if not Assigned(AStream) then exit;
+  CurrBytesPerPixel := BytesPerPixel;
+{$IFDEF FMX}
+  Map(TMapAccess.Read, Data);
+{$ELSE}
+{$ENDIF}
+  try
+    if CurrBytesPerPixel = -1 then
+    begin
+{$IFDEF FMX}
+      CurrBytesPerPixel := Data.BytesPerPixel;
+{$ELSE}
+      CurrBytesPerPixel := 3;
+      if PixelFormat = pf1bit then
+        CurrBytesPerPixel := 0;
+      if PixelFormat = pf4bit then
+        CurrBytesPerPixel := 2;
+      if PixelFormat = pf8bit then
+        CurrBytesPerPixel := 2;
+      if PixelFormat = pf15bit then
+        CurrBytesPerPixel := 2;
+      if PixelFormat = pf16bit then
+        CurrBytesPerPixel := 2;
+      if PixelFormat = pf24bit then
+        CurrBytesPerPixel := 3;
+      if PixelFormat = pf32bit then
+        CurrBytesPerPixel := 4;
+{$ENDIF}
+      if not (CurrBytesPerPixel in [0,1,2,4]) then
+        CurrBytesPerPixel := 3;
+    end;
+    if not (CurrBytesPerPixel in [0,1,2,3,4]) then
+      exit;
+    //不打算支持 8 位的。
+    if CurrBytesPerPixel = 1 then exit;
+{$IFDEF FMX}
+    nBmpWidth := Data.Width;
+    nBmpHeight := Data.Height;
+{$ELSE}
+    nBmpWidth := Width;
+    nBmpHeight := Height;
+{$ENDIF}
+    // 像素扫描
+    if CurrBytesPerPixel > 0 then
+    begin
+      wWidth := nBmpWidth * CurrBytesPerPixel;
+      if (wWidth mod 4) > 0 then
+      begin
+        wWidth := wWidth +  4 - (wWidth mod 4);
+      end;
+    end
+    else if (nBmpWidth mod 32) > 0 then
+    begin
+      wWidth := ((nBmpWidth div 32) + 1) * 4;;
+    end
+    else
+    begin
+      wWidth := (nBmpWidth div 8);
+    end;
+    bufferSize := nBmpHeight * wWidth;
+    // bmp文件头
+    BMF.bfType := $4D42;
+    BMF.bfSize := 14 + 40 + bufferSize;
+    BMF.bfReserved1 := 0;
+    BMF.bfReserved2 := 0;
+    BMF.bfOffBits := 14 + 40;
+    if (CurrBytesPerPixel = 0) then
+    begin
+      BMF.bfOffBits := BMF.bfOffBits + 2 * Sizeof(RGBQUAD);
+      BMF.bfSize := BMF.bfSize + 2 * Sizeof(RGBQUAD);
+    end;
+    if (CurrBytesPerPixel = 1) then
+    begin
+      BMF.bfOffBits := BMF.bfOffBits + 256 * Sizeof(RGBQUAD);
+      BMF.bfSize := BMF.bfSize + 256 * Sizeof(RGBQUAD);
+    end;
+    if (CurrBytesPerPixel = 2) then
+    begin
+      //多谢 [西安]老一门(yyimen@foxmail.com) 提供的 565 格式说明。
+      BMF.bfOffBits := BMF.bfOffBits + RGB565ExtDataLen;
+      BMF.bfSize := BMF.bfSize + RGB565ExtDataLen;
+    end;
+//    FileSizeFix := 0;
+//    if (BMF.bfSize mod 4) > 0 then
+//    begin
+//      FileSizeFix := 4 - BMF.bfSize mod 4;
+//    end;
+//    bufferSize := bufferSize + FileSizeFix;
+//    BMF.bfSize := BMF.bfSize + FileSizeFix;
+    // 保存bmp文件头
+    AStream.WriteBuffer(BMF, Sizeof(BMF));
+    // bmp信息头
+    FillChar(BitmapInfo, Sizeof(BitmapInfo), 0);
+    BitmapInfo.biSize := 40;
+//    if (CurrBytesPerPixel = 2) then
+//    begin
+//      //AcdSee 不支持这种大小的 BitmapInfo
+//      BitmapInfo.biSize := 40 + RGB565ExtDataLen;
+//    end;
+    BitmapInfo.biWidth := nBmpWidth;
+    BitmapInfo.biHeight := nBmpHeight;
+    BitmapInfo.biPlanes := 1;
+    if CurrBytesPerPixel > 0 then
+    begin
+      BitmapInfo.biBitCount := CurrBytesPerPixel * 8;
+    end
+    else
+    begin
+      BitmapInfo.biBitCount := 1;
+    end;
+    BitmapInfo.biSizeImage := bufferSize;
+//    if True then
+//    begin
+//      //96
+//      BitmapInfo.biXPelsPerMeter := $0EC4;
+//      BitmapInfo.biYPelsPerMeter := $0EC4;
+//    end
+//    else
+//    begin
+//      //72
+//      BitmapInfo.biXPelsPerMeter := $0B12;
+//      BitmapInfo.biYPelsPerMeter := $0B12;
+//    end;
+    BitmapInfo.biXPelsPerMeter := 0;
+    BitmapInfo.biYPelsPerMeter := 0;
+    if (CurrBytesPerPixel = 2) then
+    begin
+      //可以采用 RGB555 代替 RGB565
+      BitmapInfo.biCompression := BI_BITFIELDS; //0是 555 3 是 565
+    end;
+    // 保存bmp信息头
+    AStream.WriteBuffer(BitmapInfo, Sizeof(BitmapInfo));
+    if (CurrBytesPerPixel = 2) then
+    begin
+      // 保存 565 RGB Mask
+      A32BitData := RGB565_MASK_RED;
+      AStream.WriteBuffer(A32BitData, Sizeof(A32BitData));
+      A32BitData := RGB565_MASK_GREEN;
+      AStream.WriteBuffer(A32BitData, Sizeof(A32BitData));
+      A32BitData := RGB565_MASK_BLUE;
+      AStream.WriteBuffer(A32BitData, Sizeof(A32BitData));
+      if RGB565ExtDataLen >= 16 then
+      begin
+        A32BitData := 0;
+        AStream.WriteBuffer(A32BitData, Sizeof(A32BitData));
+      end;
+    end;
+    if (CurrBytesPerPixel = 0) or (CurrBytesPerPixel = 1) then
+    begin
+      //颜色表
+      RGBQUAD.rgbBlue := 0;
+      RGBQUAD.rgbGreen := 0;
+      RGBQUAD.rgbRed := 0;
+      RGBQUAD.rgbReserved := 0;
+      if (CurrBytesPerPixel = 1) then
+      begin
+        for I := 0 to 255 do
+        begin
+          AStream.WriteBuffer(RGBQUAD, Sizeof(RGBQUAD));
+        end;
+        BitmapInfo.biClrUsed := $FF;
+      end
+      else
+      begin
+        AStream.WriteBuffer(RGBQUAD, Sizeof(RGBQUAD));
+        RGBQUAD.rgbBlue := $FF;
+        RGBQUAD.rgbGreen := $FF;
+        RGBQUAD.rgbRed := $FF;
+        RGBQUAD.rgbReserved := 0;
+        AStream.WriteBuffer(RGBQUAD, Sizeof(RGBQUAD));
+        BitmapInfo.biClrUsed := 2;
+      end;
+    end;
+    // 像素扫描
+    SetLength(bmpData, wWidth);
+{$IFDEF FMX}
+    AlphaColorBuffer := GetMemory(nBmpWidth * SizeOf(TAlphaColor));
+    try
+{$ENDIF}
+      for nCol := nBmpHeight - 1 downto 0 do
+      begin
+        FillChar(bmpData[0], wWidth, 0);
+        wByteIdex := 0;
+        //0 是单色图
+        if (CurrBytesPerPixel = 0) or (CurrBytesPerPixel = 1) then
+        begin
+          A8BitData := 0;
+          ABitIndex := 0;
+{$IFDEF FMX}
+{$ELSE}
+          Pixs := ScanLine[nCol];
+{$ENDIF}
+          for wRow := 0 to nBmpWidth - 1 do
+          begin
+{$IFDEF FMX}
+            //X 是行坐标,Y 是 列坐标。
+            clr := Data.GetPixel(wRow, nCol);
+{$ELSE}
+{$ENDIF}
+            GrayeData := 0;
+{$IFDEF FMX}
+            if MonochromeChangeType = TMonochromeChangeType.Average then
+            begin
+              GrayeData := TAlphaColorRec(clr).R + TAlphaColorRec(clr).G + TAlphaColorRec(clr).B;
+              GrayeData := GrayeData div 3;
+            end;
+            if MonochromeChangeType = TMonochromeChangeType.Weighting then
+            begin
+              GrayeData := Round((TAlphaColorRec(clr).R * MonochromeChange_Weighting_Red +
+                 TAlphaColorRec(clr).G * MonochromeChange_Weighting_Green +
+                 TAlphaColorRec(clr).B * MonochromeChange_Weighting_Blue) / 3);
+            end;
+            if MonochromeChangeType = TMonochromeChangeType.Max then
+            begin
+              GrayeData := System.Math.Max(System.Math.Max(TAlphaColorRec(clr).R, TAlphaColorRec(clr).G),
+                                           TAlphaColorRec(clr).B);
+            end;
+{$ELSE}
+            if MonochromeChangeType = TMonochromeChangeType.Average then
+            begin
+              GrayeData := Pixs[wRow].rgbtRed + Pixs[wRow].rgbtGreen + Pixs[wRow].rgbtBlue;
+              GrayeData := GrayeData div 3;
+            end;
+            if MonochromeChangeType = TMonochromeChangeType.Weighting then
+            begin
+              GrayeData := Round((Pixs[wRow].rgbtRed * MonochromeChange_Weighting_Red +
+                 Pixs[wRow].rgbtGreen * MonochromeChange_Weighting_Green +
+                 Pixs[wRow].rgbtBlue * MonochromeChange_Weighting_Blue) / 3);
+            end;
+            if MonochromeChangeType = TMonochromeChangeType.Max then
+            begin
+              GrayeData := System.Math.Max(System.Math.Max(Pixs[wRow].rgbtRed, Pixs[wRow].rgbtGreen),
+                                           Pixs[wRow].rgbtBlue);
+            end;
+{$ENDIF}
+            if GrayeData > MonochromeChange_Threshold then
+            begin
+              A8BitData := A8BitData or ($80 shr ABitIndex);
+            end;
+            inc(ABitIndex);
+            if ABitIndex > 7 then
+            begin
+              ABitIndex := 0;
+              if (CurrBytesPerPixel = 0) then
+              begin
+                bmpData[wByteIdex] := A8BitData;
+                A8BitData := 0;
+                inc(wByteIdex);
+              end;
+            end;
+          end;
+          if (CurrBytesPerPixel = 0) then
+          begin
+            if ABitIndex > 0 then
+            begin
+              bmpData[wByteIdex] := A8BitData;
+              A8BitData := 0;
+            end;
+          end;
+        end
+        else
+        begin
+{$IFDEF FMX}
+//          for wRow := 0 to nBmpWidth - 1 do
+//          begin
+//            //X 是行坐标,Y 是 列坐标。
+//            clr := Data.GetPixel(wRow, nCol);
+//            case CurrBytesPerPixel of
+//              1:
+//              begin
+//                //不支持。
+//              end;
+//              2:
+//              begin
+//                A16BitData := rgb_24_2_565(TAlphaColorRec(clr).R, TAlphaColorRec(clr).G, TAlphaColorRec(clr).B);
+//                bmpData[wByteIdex + 0] := WordRec(A16BitData).Lo;
+//                bmpData[wByteIdex + 1] := WordRec(A16BitData).Hi;
+//              end;
+//              3,4:
+//              begin
+//                bmpData[wByteIdex + 0] := TAlphaColorRec(clr).B;
+//                bmpData[wByteIdex + 1] := TAlphaColorRec(clr).G;
+//                bmpData[wByteIdex + 2] := TAlphaColorRec(clr).R;
+//              end;
+//            end;
+//            if CurrBytesPerPixel = 4 then
+//            begin
+//              bmpData[wByteIdex + 3] := TAlphaColorRec(clr).A;
+//            end;
+//            Inc(wByteIdex, CurrBytesPerPixel);
+//          end;
+          case CurrBytesPerPixel of
+            1:
+            begin
+              //不支持。
+            end;
+            2:
+            begin
+              ScanlineToAlphaColor(Data.GetScanline(nCol), AlphaColorBuffer, nBmpWidth, Data.PixelFormat);
+              AlphaColorToScanline(AlphaColorBuffer, Addr(bmpData[0]), nBmpWidth, TPixelFormat.BGR_565);
+            end;
+            3:
+            begin
+              ScanlineToAlphaColor(Data.GetScanline(nCol), AlphaColorBuffer, nBmpWidth, Data.PixelFormat);
+              AlphaColorToScanline(AlphaColorBuffer, Addr(bmpData[0]), nBmpWidth, TPixelFormat.BGR);
+            end;
+            4:
+            begin
+              if Data.PixelFormat = TPixelFormat.BGRA then
+              begin
+                Move(PByte(Data.GetScanline(nCol))[0], bmpData[0], Data.BytesPerPixel * nBmpWidth);
+              end
+              else
+              begin
+                ScanlineToAlphaColor(Data.GetScanline(nCol), AlphaColorBuffer, nBmpWidth, Data.PixelFormat);
+                AlphaColorToScanline(AlphaColorBuffer, Addr(bmpData[0]), nBmpWidth, TPixelFormat.BGRA);
+              end;
+            end;
+          end;
+{$ELSE}
+          Pixs := ScanLine[nCol];
+          for wRow := 0 to nBmpWidth - 1 do
+          begin
+            case CurrBytesPerPixel of
+              1:
+              begin
+                //不支持。
+              end;
+              2:
+              begin
+                A16BitData := rgb_24_2_565(Pixs[wRow].rgbtRed, Pixs[wRow].rgbtGreen, Pixs[wRow].rgbtBlue);
+                bmpData[wByteIdex + 0] := WordRec(A16BitData).Lo;
+                bmpData[wByteIdex + 1] := WordRec(A16BitData).Hi;
+              end;
+              3,4:
+              begin
+                bmpData[wByteIdex + 0] := Pixs[wRow].rgbtBlue;
+                bmpData[wByteIdex + 1] := Pixs[wRow].rgbtGreen;
+                bmpData[wByteIdex + 2] := Pixs[wRow].rgbtRed;
+              end;
+            end;
+            if CurrBytesPerPixel = 4 then
+            begin
+              bmpData[wByteIdex + 3] := $FF;
+            end;
+            Inc(wByteIdex, CurrBytesPerPixel);
+          end;
+{$ENDIF}
+        end;
+        AStream.WriteBuffer(bmpData, wWidth);
+      end;
+{$IFDEF FMX}
+    finally
+      FreeMemory(AlphaColorBuffer);
+    end;
+{$ENDIF}
+//    A8BitData := 0;
+//    for I := 0 to FileSizeFix - 1 do
+//    begin
+//      AStream.WriteBuffer(A8BitData, 1);
+//    end;
+    Result := True;
+  finally
+{$IFDEF FMX}
+    Unmap(Data);
+{$ELSE}
+{$ENDIF}
+  end;
+end;
+
+procedure TKngStrBitmapHelper.SaveAsBMPToStreamDef(Stream: TStream);
+begin
+  if not SaveAsBMPToStream(Stream) then
+  begin
+{$IFDEF FMX}
+    raise EBitmapSavingFailed.Create(SBitmapSavingFailed);
+{$ELSE}
+    raise EInvalidGraphicOperation.Create(SBitmapSavingFailed);
+{$ENDIF}
+  end;
+end;
+
+{$IFDEF FMX}
+procedure TKngStrBitmapHelper.SyncAssign(Source: TPersistent);
+begin
+  TThread.Synchronize(nil,
+    procedure
+    begin
+      Assign(Source);
+    end)
+end;
+
+procedure TKngStrBitmapHelper.SyncLoadFromFile(const AFileName: string);
+var
+  Surf: TBitmapSurface;
+begin
+  Surf := TBitmapSurface.Create;
+  try
+    if TBitmapCodecManager.LoadFromFile(AFileName, Surf, CanvasClass.GetAttribute(TCanvasAttribute.MaxBitmapSize)) then
+      TThread.Synchronize(nil,
+        procedure
+        begin
+          Assign(Surf);
+        end)
+    else
+      raise EBitmapLoadingFailed.CreateFMT(SBitmapLoadingFailedNamed, [AFileName]);
+  finally
+    Surf.Free;
+  end;
+end;
+
+procedure TKngStrBitmapHelper.SyncLoadFromStream(Stream: TStream);
+var
+  S: TStream;
+  Surf: TBitmapSurface;
+begin
+  if Stream.Position > 0 then
+  begin
+    // need to create temp stream
+    S := TMemoryStream.Create;
+    try
+      S.CopyFrom(Stream, Stream.Size - Stream.Position);
+      S.Position := 0;
+      Surf := TBitmapSurface.Create;
+      try
+        if TBitmapCodecManager.LoadFromStream(S, Surf, CanvasClass.GetAttribute(TCanvasAttribute.MaxBitmapSize)) then
+          TThread.Synchronize(nil,
+            procedure
+            begin
+              Assign(Surf);
+            end)
+        else
+          raise EBitmapLoadingFailed.Create(SBitmapLoadingFailed);
+      finally
+        Surf.Free;
+      end;
+    finally
+      S.Free;
+    end;
+  end
+  else
+    if Stream.Size = 0 then
+      Clear(0)
+    else begin
+      Surf := TBitmapSurface.Create;
+      try
+        if TBitmapCodecManager.LoadFromStream(Stream, Surf, CanvasClass.GetAttribute(TCanvasAttribute.MaxBitmapSize)) then
+        TThread.Synchronize(nil,
+          procedure
+          begin
+            Assign(Surf);
+          end)
+        else
+          raise EBitmapLoadingFailed.Create(SBitmapLoadingFailed);
+      finally
+        Surf.Free;
+      end;
+    end;
+end;
+
+procedure TKngStrBitmapHelper.SyncLoadThumbnailFromFile(const AFileName: string; const AFitWidth, AFitHeight: Single;
+  const UseEmbedded: Boolean = True);
+var
+  Surf: TBitmapSurface;
+begin
+  Surf := TBitmapSurface.Create;
+  try
+    if TBitmapCodecManager.LoadThumbnailFromFile(AFileName, AFitWidth, AFitHeight, UseEmbedded, Surf) then
+      TThread.Synchronize(nil,
+        procedure
+        begin
+          Assign(Surf);
+        end)
+    else
+      raise EThumbnailLoadingFailed.CreateFMT(SThumbnailLoadingFailedNamed, [AFileName]);
+  finally
+    Surf.Free;
+  end;
+end;
+
+procedure TKngStrBitmapHelper.SyncLoadThumbnailFromStream(Stream: TStream;
+  const AFitWidth, AFitHeight: Single; const UseEmbedded: Boolean; const AutoCut: Boolean);
+var
+  S: TStream;
+  Surf: TBitmapSurface;
+begin
+  if Stream.Position > 0 then
+  begin
+    // need to create temp stream
+    S := TMemoryStream.Create;
+    try
+      S.CopyFrom(Stream, Stream.Size - Stream.Position);
+      S.Position := 0;
+      Surf := TBitmapSurface.Create;
+      try
+        if TBitmapCodecManager.LoadThumbnailFromStream(S, AFitWidth, AFitHeight, UseEmbedded, AutoCut, Surf) then
+          TThread.Synchronize(nil,
+            procedure
+            begin
+              Assign(Surf);
+            end)
+        else
+          raise EBitmapLoadingFailed.Create(SThumbnailLoadingFailed);
+      finally
+        Surf.Free;
+      end;
+    finally
+      S.Free;
+    end;
+  end
+  else
+    if Stream.Size = 0 then
+      Clear(0)
+    else begin
+      Surf := TBitmapSurface.Create;
+      try
+        if TBitmapCodecManager.LoadThumbnailFromStream(Stream, AFitWidth, AFitHeight, UseEmbedded, AutoCut, Surf) then
+          TThread.Synchronize(nil,
+            procedure
+            begin
+              Assign(Surf);
+            end)
+        else
+          raise EBitmapLoadingFailed.Create(SThumbnailLoadingFailed);
+      finally
+        Surf.Free;
+      end;
+    end;
+end;
+
+{ TKngStrBitmapCodecManager }
+
+class function TKngStrBitmapCodecManager.GetImageSize(const AStream: TStream): TPointF;
+var
+  CodecClass: TCustomBitmapCodecClass;
+  DataType: String;
+begin
+  DataType := TImageTypeChecker.GetType(AStream);
+  CodecClass := GuessCodecClass(DataType, TBitmapCodecDescriptorField.Extension);
+  if CodecClass <> nil then
+    {$IFDEF ANDROID}
+    Result := TBitmapCodecAndroid(CodecClass).GetImageSize(AStream)
+    {$ELSE}
+    Result := TBitmapCodecWIC(CodecClass).GetImageSize(AStream)
+    {$ENDIF}
+  else
+    Result := TPointF.Zero;
+end;
+
+class function TKngStrBitmapCodecManager.GetImageSize(const AFileName: string): TPointF;
+begin
+  Result := inherited GetImageSize(AFileName);
+end;
+
+class function TKngStrBitmapCodecManager.GuessCodecClass(const Name: string;
+  const Field: TBitmapCodecDescriptorField): TCustomBitmapCodecClass;
+type
+   TPrivateMethodType = function (const Name: string; const Field: TBitmapCodecDescriptorField):
+      TCustomBitmapCodecClass of object;
+var
+   AMethod: TMethod;
+   AInvoke: TPrivateMethodType absolute AMethod;
+begin
+  AMethod.Code := @TBitmapCodecManager.GuessCodecClass;
+  AMethod.Data := Self;
+  Result := AInvoke(Name, Field);
+end;
+
+class function TKngStrBitmapCodecManager.LoadFromStream(const AStream: TStream;
+  const Bitmap: TBitmapSurface; const MaxSizeLimit: Cardinal): Boolean;
+var
+  CodecClass: TCustomBitmapCodecClass;
+  Codec: TCustomBitmapCodec;
+  DataType: String;
+begin
+  Result := False;
+  DataType := TImageTypeChecker.GetType(AStream);
+  CodecClass := GuessCodecClass(DataType, TBitmapCodecDescriptorField.Extension);
+  if CodecClass <> nil then
+  begin
+    Codec := CodecClass.Create;
+    try
+      {$IFDEF ANDROID}
+      Result := TBitmapCodecAndroid(Codec).LoadFromStream(AStream, Bitmap, MaxSizeLimit);
+      {$ELSE}
+      Result := TBitmapCodecWIC(Codec).LoadFromStream(AStream, Bitmap, MaxSizeLimit);
+      {$ENDIF}
+    finally
+      Codec.Free;
+    end;
+  end
+end;
+
+class function TKngStrBitmapCodecManager.LoadThumbnailFromStream(
+  const AStream: TStream; const AFitWidth, AFitHeight: Single;
+  const UseEmbedded: Boolean; const AutoCut: Boolean;
+  const Bitmap: TBitmapSurface): Boolean;
+var
+  CodecClass: TCustomBitmapCodecClass;
+  Codec: TCustomBitmapCodec;
+  DataType: String;
+begin
+  Result := False;
+  DataType := TImageTypeChecker.GetType(AStream);
+  CodecClass := GuessCodecClass(DataType, TBitmapCodecDescriptorField.Extension);
+  if CodecClass <> nil then
+  begin
+    Codec := CodecClass.Create;
+    try
+      {$IFDEF ANDROID}
+      Result := TBitmapCodecAndroid(Codec).LoadThumbnailFromStream(AStream, AFitWidth, AFitHeight, UseEmbedded, AutoCut, Bitmap);
+      {$ELSE}
+      Result := TBitmapCodecWIC(Codec).LoadThumbnailFromStream(AStream, AFitWidth, AFitHeight, UseEmbedded, AutoCut, Bitmap);
+      {$ENDIF}
+    finally
+      Codec.Free;
+    end;
+  end
+end;
+
+{$IFDEF ANDROID}
+
+{ TBitmapCodecAndroid }
+
+class function TBitmapCodecAndroid.GetMovieSize(const Stream: TStream): TPoint;
+type
+   TPrivateMethodType = function (const Stream: TStream): TPoint of object;
+var
+   AMethod: TMethod;
+   AInvoke: TPrivateMethodType absolute AMethod;
+begin
+  AMethod.Code := @TBitmapCodecAndroid.GetMovieSize;
+  AMethod.Data := Self;
+  Result := AInvoke(Stream);
+end;
+
+
+class function TBitmapCodecAndroid.GetImageSize(const AStream: TStream): TPointF;
+var
+  TempStream: TMemoryStream;
+  TempArray: TJavaArray<Byte>;
+  NativeBitmap: JBitmap;
+  LoadOptions: JBitmapFactory_Options;
+  SavePosition: Int64;
+begin
+  if IsGIFStream(AStream) then
+    Result := GetMovieSize(AStream)
+  else begin
+    SavePosition := AStream.Position;
+    try
+      TempStream := TMemoryStream.Create;
+      try
+        TempStream.CopyFrom(AStream, AStream.Size);
+        TempArray := TJavaArray<Byte>.Create(TempStream.Size);
+        Move(TempStream.Memory^, TempArray.Data^, TempStream.Size);
+      finally
+        TempStream.Free;
+      end;
+
+      LoadOptions := TJBitmapFactory_Options.JavaClass.init;
+      LoadOptions.inJustDecodeBounds := True;
+      TJBitmapFactory.JavaClass.decodeByteArray(TempArray, 0, TempArray.Length, LoadOptions);
+      TempArray := nil;
+      Result := TPointF.Create(LoadOptions.outWidth, LoadOptions.outHeight);
+    finally
+      AStream.Position := SavePosition;
+    end;
+  end;
+end;
+
+class function TBitmapCodecAndroid.GetImageSize(const AFileName: string): TPointF;
+begin
+  Result := inherited GetImageSize(AFileName);
+end;
+
+class function TBitmapCodecAndroid.IsGIFStream(const Stream: TStream): Boolean;
+begin
+  Result := TImageTypeChecker.GetType(Stream) = SGIFImageExtension;
+end;
+
+function TBitmapCodecAndroid.LoadMovieFromStream(const Stream: TStream; const Surface: TBitmapSurface): Boolean;
+var
+  PrevPosition: Int64;
+  TempArray: TJavaArray<Byte>;
+  Movie: JMovie;
+  Bitmap: JBitmap;
+  Canvas: JCanvas;
+begin
+  PrevPosition := Stream.Position;
+  try
+    TempArray := TJavaArray<Byte>.Create(Stream.Size - Stream.Position);
+    Stream.ReadBuffer(TempArray.Data^, TempArray.Length);
+  finally
+    Stream.Position := PrevPosition;
+  end;
+
+  Movie := TJMovie.JavaClass.decodeByteArray(TempArray, 0, TempArray.Length);
+  TempArray := nil;
+
+  Bitmap := TJBitmap.JavaClass.createBitmap(Movie.width, Movie.height, TJBitmap_Config.JavaClass.ARGB_8888);
+  //kngstr
+  if Bitmap = nil then
+    Exit(False);
+  try
+    Canvas := TJCanvas.JavaClass.init(Bitmap);
+    try
+      Movie.setTime(0);
+      Movie.draw(Canvas, 0, 0);
+    finally
+      Canvas := nil;
+    end;
+
+    Result := JBitmapToSurface(Bitmap, Surface);
+  finally
+    Bitmap.recycle;
+  end;
+end;
+
+function TBitmapCodecAndroid.StretchIfNeed(const SrcBitmap: JBitmap; const Bitmap: TBitmapSurface;
+  const LoadOptions: JBitmapFactory_Options; const MaxSizeLimit: Cardinal): Boolean;
+var
+  R: TRectF;
+  ScaledBitmap: JBitmap;
+begin
+  if (MaxSizeLimit > 0) and ((LoadOptions.outWidth > Integer(MaxSizeLimit)) or
+    (LoadOptions.outHeight > Integer(MaxSizeLimit))) then
+  begin
+    R := TRectF.Create(0, 0, LoadOptions.outWidth, LoadOptions.outHeight);
+    R.Fit(TRectF.Create(0, 0, MaxSizeLimit, MaxSizeLimit));
+    ScaledBitmap := TJBitmap.JavaClass.createScaledBitmap(SrcBitmap, R.Truncate.Width, R.Truncate.Height, True);
+    //kngstr
+    if ScaledBitmap = nil then
+      Exit(False);
+    try
+      Result := JBitmapToSurface(ScaledBitmap, Bitmap);
+    finally
+      ScaledBitmap.recycle;
+    end;
+  end
+  else
+    Result := JBitmapToSurface(SrcBitmap, Bitmap);
+end;
+
+function TBitmapCodecAndroid.LoadFromStream(const AStream: TStream;
+  const Bitmap: TBitmapSurface; const MaxSizeLimit: Cardinal): Boolean;
+var
+  TempStream: TMemoryStream;
+  TempArray: TJavaArray<Byte>;
+  NativeBitmap: JBitmap;
+  LoadOptions: JBitmapFactory_Options;
+  SavePosition: Int64;
+begin
+  if IsGIFStream(AStream) then
+    Result := LoadMovieFromStream(AStream, Bitmap)
+  else
+  begin
+    SavePosition := AStream.Position;
+    try
+      TempStream := TMemoryStream.Create;
+      try
+        TempStream.CopyFrom(AStream, AStream.Size);
+        TempArray := TJavaArray<Byte>.Create(TempStream.Size);
+        Move(TempStream.Memory^, TempArray.Data^, TempStream.Size);
+      finally
+        TempStream.Free;
+      end;
+
+      LoadOptions := TJBitmapFactory_Options.JavaClass.init;
+      NativeBitmap := TJBitmapFactory.JavaClass.decodeByteArray(TempArray, 0, TempArray.Length, LoadOptions);
+      TempArray := nil;
+      //kngstr
+      if NativeBitmap = nil then
+        Exit(False);
+      try
+        if (LoadOptions.outWidth < 1) or (LoadOptions.outHeight < 1) then
+          Exit(False);
+
+        Result := StretchIfNeed(NativeBitmap, Bitmap, LoadOptions, MaxSizeLimit);
+      finally
+        NativeBitmap.recycle;
+      end;
+    finally
+      AStream.Position := SavePosition;
+    end;
+  end;
+end;
+
+function TBitmapCodecAndroid.LoadMovieFromStreamScaled(const AStream: TStream; const Surface: TBitmapSurface;
+  const FitSize: TPoint): Boolean;
+var
+  TempStream: TMemoryStream;
+  TempArray: TJavaArray<Byte>;
+  Movie: JMovie;
+  OrigBitmap, Bitmap: JBitmap;
+  Canvas: JCanvas;
+  OrigSize: TPoint;
+  LoadOptions: JBitmapFactory_Options;
+  SavePosition: Int64;
+begin
+  SavePosition := AStream.Position;
+  try
+    TempStream := TMemoryStream.Create;
+    try
+      TempStream.CopyFrom(AStream, AStream.Size);
+      TempArray := TJavaArray<Byte>.Create(TempStream.Size);
+      Move(TempStream.Memory^, TempArray.Data^, TempStream.Size);
+    finally
+      TempStream.Free;
+    end;
+
+    Movie := TJMovie.JavaClass.decodeByteArray(TempArray, 0, TempArray.Length);
+    TempArray := nil;
+
+    OrigSize := TPoint.Create(Movie.width, Movie.height);
+    OrigBitmap := TJBitmap.JavaClass.createBitmap(OrigSize.X, OrigSize.Y, TJBitmap_Config.JavaClass.ARGB_8888);
+    if OrigBitmap = nil then //kngstr fixed
+      Exit(False);
+    try
+      Canvas := TJCanvas.JavaClass.init(OrigBitmap);
+      try
+        Movie.setTime(0);
+        Movie.draw(Canvas, 0, 0);
+      finally
+        Canvas := nil;
+      end;
+
+      Movie := nil;
+
+      Bitmap := TJBitmap.JavaClass.createBitmap(FitSize.X, FitSize.Y, TJBitmap_Config.JavaClass.ARGB_8888);
+      if Bitmap = nil then //kngstr fixed
+        Exit(False);
+      try
+        Canvas := TJCanvas.JavaClass.init(Bitmap);
+        try
+          Canvas.drawBitmap(OrigBitmap, TJRect.JavaClass.init(0, 0, OrigSize.X, OrigSize.Y), TJRect.JavaClass.init(0, 0,
+            FitSize.X, FitSize.y), nil);
+        finally
+          Canvas := nil;
+        end;
+
+        Result := JBitmapToSurface(Bitmap, Surface);
+      finally
+        Bitmap.recycle; //kngstr fixed
+      end;
+    finally
+      OrigBitmap.recycle;
+    end;
+  finally
+    AStream.Position := SavePosition;
+  end;
+end;
+
+//连续decode需要reset
+//https://blog.csdn.net/boystray/article/details/77725648
+//多图加载的优化
+//https://blog.csdn.net/Android_app/article/details/45815093
+//inSampleSize优化
+//https://www.jianshu.com/p/f15cd2ed6ec0
+procedure calculateInSampleSize(options: JBitmapFactory_Options; reqWidth, reqHeight: Integer);
+var
+  width, height, suitedValue: Integer;
+  widthRatio, heightRatio: Integer;
+begin
+  options.inSampleSize := 1;
+
+  width := options.outWidth;
+  height := options.outHeight;
+
+  if (height > reqHeight) or (width > reqWidth) then begin
+    //使用需要的宽高的最大值来计算比率
+    if reqHeight > reqWidth then
+      suitedValue := reqHeight
+    else
+      suitedValue := reqWidth;
+    heightRatio := height div suitedValue;
+    widthRatio := width div suitedValue;
+
+    if heightRatio > widthRatio then //用最大
+      options.inSampleSize := heightRatio
+    else
+      options.inSampleSize := widthRatio;
+  end;
+end;
+
+function TBitmapCodecAndroid.LoadThumbnailFromStream(const AStream: TStream;
+  const AFitWidth, AFitHeight: Single; const UseEmbedded: Boolean;
+  const AutoCut: Boolean; const Bitmap: TBitmapSurface): Boolean;
+var
+  TempStream: TMemoryStream;
+  TempArray: TJavaArray<Byte>;
+  NativeBitmap1, NativeBitmap2, NativeBitmap3: JBitmap;
+  LoadOptions: JBitmapFactory_Options;
+  SavePosition: Int64;
+  X, Y, W, H: Integer;
+begin
+  if IsGIFStream(AStream) then
+    Result := LoadMovieFromStreamScaled(AStream, Bitmap, TPoint.Create(Round(AFitWidth), Round(AFitHeight)))
+  else
+  begin
+    SavePosition := AStream.Position;
+    try
+      TempStream := TMemoryStream.Create;
+      try
+        TempStream.CopyFrom(AStream, AStream.Size);
+        TempArray := TJavaArray<Byte>.Create(TempStream.Size);
+        Move(TempStream.Memory^, TempArray.Data^, TempStream.Size);
+      finally
+        TempStream.Free;
+      end;
+
+      LoadOptions := TJBitmapFactory_Options.JavaClass.init;
+      //读取文件大小
+      LoadOptions.inJustDecodeBounds := True;
+      TJBitmapFactory.JavaClass.decodeByteArray(TempArray, 0, TempArray.Length, LoadOptions);
+      //计算缩略尺寸
+      calculateInSampleSize(LoadOptions, Round(AFitWidth), Round(AFitHeight));
+      LoadOptions.inJustDecodeBounds := False;
+      NativeBitmap1 := TJBitmapFactory.JavaClass.decodeByteArray(TempArray, 0, TempArray.Length, LoadOptions);
+      TempArray := nil;
+      //kngstr
+      if NativeBitmap1 = nil then
+        Exit(False);
+      try
+        if (LoadOptions.outWidth < 1) or (LoadOptions.outHeight < 1) then
+          Exit(False);
+
+        NativeBitmap2 := TJBitmap.JavaClass.createScaledBitmap(NativeBitmap1, Round(AFitWidth), Round(AFitHeight), True);
+        //kngstr
+        if NativeBitmap2 = nil then
+          Exit(False);
+        try
+          if not AutoCut then
+            Result := JBitmapToSurface(NativeBitmap2, Bitmap)
+          else begin //临时处理下,截取大图的中间部分,尤其是分辨率特别大的
+            X := 0;
+            Y := 0;
+            W := Round(AFitWidth);
+            H := Round(AFitHeight);
+            if W > H then begin
+              X := (W - H) div 2;
+              W := H;
+            end
+            else if W < H then begin
+              Y := (H - W) div 2;
+              H := W;
+            end;
+
+            NativeBitmap3 := TJBitmap.JavaClass.createBitmap(NativeBitmap2, X, Y, W, H);
+            //kngstr
+            if NativeBitmap3 = nil then
+              Exit(False);
+            try
+              Result := JBitmapToSurface(NativeBitmap3, Bitmap);
+            finally
+              NativeBitmap3.recycle;
+            end;
+          end;
+        finally
+          NativeBitmap2.recycle;
+        end;
+      finally
+        NativeBitmap1.recycle;
+      end;
+    finally
+      AStream.Position := SavePosition;
+    end;
+  end;
+end;
+{$ENDIF}
+
+{$ENDIF}
+
+{$IFDEF FMX}
+{ TBitmap16bitFiler }
+
+class constructor TBitmap16bitFiler.Create;
+var
+  i: integer;
+begin
+  i := 0;
+  while i < $10000 do
+  begin
+    // 不用for因为优化会将循环变量的值保存在寄存器不更新到内存
+    Color[i] := PixelToAlphaColor(@i, TPixelFormat.BGR_565);
+    inc(i);
+  end;
+end;
+
+class procedure TBitmap16bitFiler.FillScanLine(D: TBitmapData;
+  ScanLine, Width: integer; data: Pointer);
+var
+  SC: PAlphaColorArray;
+  DA: PWordArray;
+  I: Integer;
+  MinWidth: Integer;
+begin
+  SC := D.GetScanline(ScanLine);
+  DA := data;
+  MinWidth := D.Width;
+  if (Width > 0) and (Width < MinWidth) then
+    MinWidth := Width;
+  for I := 0 to MinWidth - 1 do
+  begin
+    if D.PixelFormat = TPixelFormat.BGRA then
+    begin
+      SC[I] := Color[DA[I]];
+    end
+    else
+    begin
+      AlphaColorToPixel(Color[DA[I]], Addr(SC[I]), D.PixelFormat);
+    end;
+  end;
+end;
+
+function FillScanLineFormMemory(BitmapData: TBitmapData; ScanLineIndex, Width: integer;
+      InputData: Pointer; InputFormat: TPixelFormat): Boolean;
+var
+  SC: PAlphaColorArray;
+  Buffer: PAlphaColor;
+  MinWidth: Integer;
+begin
+  Result := False;
+  if ScanLineIndex < 0 then exit;
+  if ScanLineIndex >= BitmapData.Height then exit;
+  
+  SC := BitmapData.GetScanline(ScanLineIndex);
+  MinWidth := BitmapData.Width;
+  if (Width > 0) and (Width < MinWidth) then
+    MinWidth := Width;
+  if InputFormat = BitmapData.PixelFormat then
+  begin
+    Move(PByte(InputData)[0], SC[0], BitmapData.BytesPerPixel * MinWidth);
+  end
+  else
+  begin
+    Buffer := GetMemory(MinWidth * SizeOf(TAlphaColor));
+    try
+      ScanlineToAlphaColor(InputData, Buffer, MinWidth, InputFormat);
+      AlphaColorToScanline(Buffer, SC, MinWidth, BitmapData.PixelFormat);
+    finally
+      FreeMemory(Buffer);
+    end;
+  end;
+  Result := True;
+end;
+
+{$ENDIF}
+
+{ TBitmapCodecWIC }
+
+{$IFDEF MSWINDOWS}
+function TBitmapCodecWIC.DecodeFrame(const Frame: IWICBitmapFrameDecode;
+  const Bitmap: TBitmapSurface; const MaxSizeLimit: Cardinal): Boolean;
+type
+   TPrivateMethodType = function (const Frame: IWICBitmapFrameDecode;
+  const Bitmap: TBitmapSurface; const MaxSizeLimit: Cardinal): Boolean of object;
+var
+   AMethod: TMethod;
+   AInvoke: TPrivateMethodType absolute AMethod;
+begin
+  AMethod.Code := @TBitmapCodecWIC.DecodeFrame;
+  AMethod.Data := Self;
+  Result := AInvoke(Frame, Bitmap, MaxSizeLimit);
+end;
+
+
+class function TBitmapCodecWIC.GetImageSize(const AStream: TStream): TPointF;
+var
+  Decoder: IWICBitmapDecoder;
+  Frame: IWICBitmapFrameDecode;
+  W, H: UINT;
+  CopyStream: TMemoryStream;
+  Stream: IWICStream;
+  SavePosition: Int64;
+begin
+  W := 0;
+  H := 0;
+  SavePosition := AStream.Position;
+  try
+    CopyStream := TMemoryStream.Create;
+    try
+      CopyStream.CopyFrom(AStream, AStream.Size);
+
+      ImagingFactory.CreateStream(Stream);
+      Stream.InitializeFromMemory(CopyStream.Memory, CopyStream.Size);
+
+      ImagingFactory.CreateDecoderFromStream(stream, GUID_NULL, WICDecodeMetadataCacheOnDemand, Decoder);
+      if Decoder <> nil then
+      begin
+        Decoder.GetFrame(0, Frame);
+        if Frame <> nil then
+          Frame.GetSize(W, H);
+      end;
+      Result := PointF(W, H);
+    finally
+      CopyStream.Free;
+    end;
+  finally
+    AStream.Position := SavePosition;
+  end;
+end;
+
+function TBitmapCodecWIC.LoadThumbnailFromStream(const AStream: TStream;
+  const AFitWidth, AFitHeight: Single; const UseEmbedded, AutoCut: Boolean;
+  const Bitmap: TBitmapSurface): Boolean;
+var
+  Decoder: IWICBitmapDecoder;
+  CopyStream: TMemoryStream;
+  Stream: IWICStream;
+  Frame: IWICBitmapFrameDecode;
+  Bmp: IWICBitmapSource;
+  Converter: IWICFormatConverter;
+  Scaler: IWICBitmapScaler;
+  R: TRectF;
+  Width, Height: UINT;
+begin
+  Result := False;
+  CopyStream := TMemoryStream.Create;
+  try
+    CopyStream.CopyFrom(AStream, AStream.Size);
+
+    ImagingFactory.CreateStream(Stream);
+    Stream.InitializeFromMemory(CopyStream.Memory, CopyStream.Size);
+
+    //kngstr
+    ImagingFactory.CreateDecoderFromStream(Stream, GUID_NULL, WICDecodeMetadataCacheOnDemand, Decoder);
+    if Decoder <> nil then
+    begin
+      Decoder.GetFrame(0, Frame);
+      if UseEmbedded then
+        Frame.GetThumbnail(Bmp);
+      if Bmp <> nil then
+      begin
+        ImagingFactory.CreateFormatConverter(Converter);
+        if Succeeded(Converter.Initialize(Bmp, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, nil, 0, 0)) then
+        begin
+          Converter.GetSize(Width, Height);
+
+          Bitmap.SetSize(Width, Height, TPixelFormat.BGRA);
+          Result := Succeeded(Converter.CopyPixels(nil, Bitmap.Pitch, Height * Cardinal(Bitmap.Pitch),
+            PByte(Bitmap.Bits)));
+        end;
+      end
+      else
+      if Frame <> nil then
+      begin
+        Frame.GetSize(Width, Height);
+
+        R := TRectF.Create(0, 0, Width, Height);
+        R.Fit(TRectF.Create(0, 0, AFitWidth, AFitHeight));
+
+        ImagingFactory.CreateBitmapScaler(Scaler);
+        if Succeeded(Scaler.Initialize(frame, Trunc(R.Width), Trunc(R.Height), WICBitmapInterpolationModeLinear)) then
+        begin
+          ImagingFactory.CreateFormatConverter(Converter);
+          if Succeeded(Converter.Initialize(scaler, GUID_WICPixelFormat32bppPBGRA,
+            WICBitmapDitherTypeNone, nil, 0, 0)) then
+          begin
+            Converter.GetSize(Width, Height);
+
+            Bitmap.SetSize(Width, Height, TPixelFormat.BGRA);
+            Result := Succeeded(Converter.CopyPixels(nil, Bitmap.Pitch, Height * Cardinal(Bitmap.Pitch),
+              PByte(Bitmap.Bits)));
+          end;
+        end;
+      end;
+    end;
+  finally
+    CopyStream.Free;
+  end;
+end;
+{$ENDIF}
+
+end.

+ 38 - 0
ksFmxUtils.pas

@@ -0,0 +1,38 @@
+unit ksFmxUtils;
+
+interface
+
+uses
+  FMX.Types;
+
+procedure FreeFmxObject(AFmxObject: TFmxObject);
+procedure FreeAndNilFmxObject(var AFmxObject);
+
+implementation
+
+uses
+  ksObjUtils;
+
+procedure FreeFmxObject(AFmxObject: TFmxObject);
+begin
+  if not Assigned(AFmxObject) then
+    Exit;
+  if Assigned(AFmxObject.Owner) then
+    AFmxObject.Owner.RemoveComponent(AFmxObject);
+  if Assigned(AFmxObject.Parent) then
+    AFmxObject.Parent := nil;
+  FreeObject(AFmxObject);
+end;
+
+procedure FreeAndNilFmxObject(var AFmxObject);
+var
+  ATemp: TFmxObject;
+begin
+  if Pointer(AFmxObject) = nil then
+    Exit;
+  ATemp := TFmxObject(AFmxObject);
+  Pointer(AFmxObject) := nil;
+  FreeFmxObject(ATemp);
+end;
+
+end.

+ 29 - 0
ksObjUtils.pas

@@ -0,0 +1,29 @@
+unit ksObjUtils;
+
+interface
+
+//take from qdac (Thanks for swish)
+procedure FreeObject(AObject: TObject);
+procedure FreeAndNilObject(var AObject);
+
+implementation
+
+procedure FreeObject(AObject: TObject);
+begin
+{$IFDEF AUTOREFCOUNT}
+  AObject.DisposeOf;
+{$ELSE}
+  AObject.Free;
+{$ENDIF}
+end;
+
+procedure FreeAndNilObject(var AObject);
+var
+  ATemp: TObject;
+begin
+  ATemp := TObject(AObject);
+  Pointer(AObject) := nil;
+  FreeObject(ATemp);
+end;
+
+end.