Browse Source

* Add system.pushnotifications for Delphi compatibility

Michaël Van Canneyt 1 year ago
parent
commit
25da420f61

+ 2 - 0
packages/vcl-compat/fpmake.pp

@@ -51,6 +51,8 @@ begin
     T:=P.Targets.AddUnit('system.diagnostics.pp');
     T:=P.Targets.AddUnit('system.diagnostics.pp');
     T:=P.Targets.AddUnit('system.notification.pp');
     T:=P.Targets.AddUnit('system.notification.pp');
     T.Dependencies.AddUnit('system.messaging');
     T.Dependencies.AddUnit('system.messaging');
+    T:=P.Targets.AddUnit('system.pushnotifications.pp');
+    T.Dependencies.AddUnit('system.messaging');
     T:=P.Targets.AddUnit('system.json.pp');
     T:=P.Targets.AddUnit('system.json.pp');
     T.ResourceStrings := True;
     T.ResourceStrings := True;
 
 

+ 478 - 0
packages/vcl-compat/src/system.pushnotifications.pp

@@ -0,0 +1,478 @@
+{
+   This file is part of the Free Pascal run time library.
+   Copyright (c) 2023 the Free Pascal development team
+
+   Generic push notification service classes.
+
+   See the file COPYING.FPC, included in this distribution,
+   for details about the copyright.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+}
+unit System.PushNotifications;
+
+interface
+
+{$MODE OBJFPC}
+{$H+}
+{$SCOPEDENUMS ON}
+{$modeswitch advancedrecords}
+
+uses
+{$IFDEF FPC_DOTTEDUNITS}
+  System.Classes, System.Sysutils, System.Contnrs, System.JSON, System.Messaging, System.Generics.Collections;
+{$ELSE}
+  classes, sysutils, contnrs, system.json, system.messaging, generics.collections;
+{$ENDIF}
+
+type
+  TPushServiceNotification = class;
+  TPushServiceManager = class;
+  TPushService = class;
+  TPushServiceConnection = class;
+
+  { TPushService }
+
+  TPushService = class abstract
+  private
+    FManager: TPushServiceManager;
+    FServiceName: string;
+    FConnections : TFPList;
+    FProps : TFPStringHashTable;
+    function GetAppProp(const aName: string): string;
+    function GetConnection(aIndex: Integer): TPushServiceConnection;
+    function GetConnectionCount: Integer;
+    function GetDeviceIDValue(const aName: string): string;
+    function GetDeviceTokenValue(const aName: string): string;
+    procedure SetAppProp(const aName: string; aValue: string);
+  public
+    type
+      TPropPair = Specialize TPair<string, string>;
+      TPropArray = Specialize TArray<TPropPair>;
+      TPushServiceNotificationArray = specialize TArray<TPushServiceNotification>;
+      TServiceNames = record
+      const
+        GCM = 'gcm';
+        FCM = 'fcm';
+        APS = 'aps';
+      end;
+      TAppPropNames = record
+      const
+        GCMAppID = 'gcmappid';
+      end;
+      TDeviceTokenNames = record
+      const
+        DeviceToken = 'devicetoken';
+      end;
+      TDeviceIDNames = record
+      const
+        DeviceID = 'deviceid';
+      end;
+  public
+    type
+      TChange = (Status, DeviceToken, StartupNotifications);
+      TChanges = set of TChange;
+      TStatus = (Stopped, Starting, Started, StartupError);
+  const
+    NeedsStartStatus = [TStatus.Stopped, TStatus.StartupError];
+  protected
+    function FindPropInArray(const aProp : String; anArray : TPropArray) : string;
+    procedure RemoveConnection(const aConnection: TPushServiceConnection);
+    procedure AddConnection(const aConnection: TPushServiceConnection);
+    procedure DoChange(aChanges: TChanges);
+    procedure DoReceiveNotification(const aNotification: TPushServiceNotification);
+    function GetStatus: TStatus; virtual; abstract;
+    function NeedsStart : Boolean;
+    procedure StartService; virtual; abstract;
+    procedure StopService; virtual; abstract;
+    function GetDeviceToken: TPropArray; virtual; abstract;
+    function GetDeviceID: TPropArray; virtual; abstract;
+    function GetStartupNotifications: TPushServiceNotificationArray; virtual; abstract;
+    function GetStartupError: string; virtual; abstract;
+  public
+    constructor Create(const aOwner: TPushServiceManager; const aServiceName: string); virtual;
+    procedure AfterConstruction; override;
+    destructor Destroy; override;
+    function CreateConnection: TPushServiceConnection;
+    function IndexOfConnection(const aConnection: TPushServiceConnection): Integer;
+    property ServiceName: string read FServiceName;
+    property Manager: TPushServiceManager read FManager;
+    property ConnectionCount: Integer read GetConnectionCount;
+    property Connections[aIndex: Integer]: TPushServiceConnection read GetConnection;
+    property Status: TStatus read GetStatus;
+    property AppProps[const aName: string]: string read GetAppProp write SetAppProp;
+    property DeviceToken: TPropArray read GetDeviceToken;
+    property DeviceTokenValue[const aName: string]: string read GetDeviceTokenValue;
+    property DeviceID: TPropArray read GetDeviceID;
+    property DeviceIDValue[const aName: string]: string read GetDeviceIDValue;
+    property StartupNotifications: TPushServiceNotificationArray read GetStartupNotifications;
+    property StartupError: string read GetStartupError;
+  end;
+
+  { TPushServiceConnection }
+
+  TPushServiceConnection = class sealed
+  public
+    type
+      TReceiveNotificationEvent = procedure(Sender: TObject; const aNotification: TPushServiceNotification) of object;
+      TChangeEvent = procedure(Sender: TObject; AChange: TPushService.TChanges) of object;
+  private
+    FActive: Boolean;
+    FOnChange: TChangeEvent;
+    FOnReceiveNotification: TReceiveNotificationEvent;
+    FService: TPushService;
+    procedure SetActive(AValue: Boolean);
+  protected
+    procedure DoChange(AChanges: TPushService.TChanges);
+    procedure DoReceiveNotification(const aNotification: TPushServiceNotification);
+  public
+    constructor Create(const aService: TPushService); virtual;
+    destructor Destroy; override;
+    procedure AfterConstruction; override;
+    property Active: Boolean read FActive write SetActive;
+    property Service: TPushService read FService;
+    property OnReceiveNotification: TReceiveNotificationEvent read FOnReceiveNotification write FOnReceiveNotification;
+    property OnChange: TChangeEvent read FOnChange write FOnChange;
+  end;
+
+  { TPushServiceManager }
+
+  TPushServiceManager = class sealed
+  private
+    Type
+
+      { TServiceRegistration }
+
+      TServiceRegistration = Class
+      private
+        FOwned: Boolean;
+        FService: TPushService;
+      public
+        Constructor create(aService : TPushService; aOwned : Boolean);
+        Destructor Destroy; override;
+        Property Service : TPushService Read FService;
+        Property Owned : Boolean Read FOwned;
+      end;
+  class var
+    _Instance : TPushServiceManager;
+  private
+    FList : TFPObjectList;
+    function GetCount: Integer;
+    function GetService(aIndex: Integer): TPushService;
+  public
+    class constructor Init;
+    class destructor done;
+    constructor create;
+    destructor Destroy; override;
+    procedure AddService(aService: TPushService; aOwnsService: Boolean = True);
+    procedure RemoveService(aService: TPushService);
+    function GetServiceByName(const aServiceName: string): TPushService;
+    function IndexOfServiceByName(const aServiceName: string): Integer;
+    function IndexOfService(const aService: TPushService): Integer;
+    property Count: Integer read GetCount;
+    property Services[aIndex: Integer]: TPushService read GetService; default;
+    class property Instance: TPushServiceManager read _Instance;
+  end;
+
+  TPushServiceNotification = class abstract
+  protected
+    function GetDataKey: string; virtual; abstract;
+    function GetJson: TJSONObject; virtual; abstract;
+    function GetDataObject: TJSONObject; virtual; abstract;
+  public
+    property DataKey: string read GetDataKey;
+    property Json: TJSONObject read GetJson;
+    property DataObject: TJSONObject read GetDataObject;
+  end;
+
+  EPushServiceError = class(Exception)
+  end;
+
+
+implementation
+
+uses
+{$IFDEF FPC_DOTTEDUNIS}
+  System.Generics.Defaults;
+{$ELSE}
+  Generics.Defaults;
+{$ENDIF}
+
+Resourcestring
+  SErrServiceRegistered = 'Service %s already registered';
+  SErrConnectionNotConnectedToThisService = 'Service Connection not connected to this service';
+  SErrConnectionAlreadyConnectedToThisService = 'Service Connection already connected to this service';
+
+{ TPushService }
+
+function TPushService.GetAppProp(const aName: string): string;
+begin
+  Result:=FProps[aName];
+end;
+
+function TPushService.GetConnection(aIndex: Integer): TPushServiceConnection;
+begin
+  Result:=TPushServiceConnection(FConnections.Items[aIndex]);
+end;
+
+function TPushService.GetConnectionCount: Integer;
+begin
+  Result:=FConnections.Count;
+end;
+
+function TPushService.GetDeviceIDValue(const aName: string): string;
+begin
+  Result:=FindPropInArray(aName,DeviceID);
+end;
+
+function TPushService.GetDeviceTokenValue(const aName: string): string;
+begin
+  Result:=FindPropInArray(aName,DeviceToken);
+end;
+
+procedure TPushService.SetAppProp(const aName: string; aValue: string);
+begin
+  FProps.Items[aName]:=aValue;
+end;
+
+function TPushService.FindPropInArray(const aProp: String; anArray: TPropArray): string;
+
+var
+  P : TPropPair;
+
+begin
+  Result:='';
+  For P in anArray do
+    if SameText(aProp,P.Key) then
+      Exit(P.Value);
+end;
+
+procedure TPushService.DoChange(aChanges: TChanges);
+
+var
+  P : Pointer;
+  C : TPushServiceConnection absolute P;
+
+begin
+  For P in FConnections do
+    C.DoChange(aChanges);
+end;
+
+procedure TPushService.DoReceiveNotification(const aNotification: TPushServiceNotification);
+
+var
+  P : Pointer;
+  C : TPushServiceConnection absolute P;
+
+begin
+  For P in FConnections do
+    C.DoReceiveNotification(aNotification);
+end;
+
+function TPushService.NeedsStart: Boolean;
+begin
+  Result:=Status in NeedsStartStatus;
+end;
+
+constructor TPushService.Create(const aOwner: TPushServiceManager; const aServiceName: string);
+begin
+  FManager:=aOwner;
+  FServiceName:=aServiceName;
+  FConnections:=TFPList.Create;
+  FProps:=TFPStringHashTable.Create;
+end;
+
+procedure TPushService.AfterConstruction;
+begin
+  inherited AfterConstruction;
+  if Assigned(FManager) then
+    FManager.AddService(Self,True);
+end;
+
+destructor TPushService.Destroy;
+begin
+  if Assigned(FManager) then
+    FManager.RemoveService(Self); // Will clear FManager
+  FreeAndNil(FConnections);
+  FreeAndNil(FProps);
+  inherited Destroy;
+end;
+
+function TPushService.CreateConnection: TPushServiceConnection;
+begin
+  Result:=TPushServiceConnection.Create(Self);
+end;
+
+function TPushService.IndexOfConnection(const aConnection: TPushServiceConnection): Integer;
+begin
+  Result:=FConnections.IndexOf(aConnection);
+end;
+
+procedure TPushService.RemoveConnection(const aConnection: TPushServiceConnection);
+begin
+  if not (aConnection.FService=Self) then
+    raise EPushServiceError.Create(SErrConnectionNotConnectedToThisService);
+  FConnections.Remove(aConnection);
+end;
+
+procedure TPushService.AddConnection(const aConnection: TPushServiceConnection);
+begin
+  if FConnections.IndexOf(aConnection)<>-1 then
+    raise EPushServiceError.Create(SErrConnectionAlreadyConnectedToThisService);
+  aConnection.FService:=Self;
+  FConnections.Add(aConnection);
+end;
+
+{ TPushServiceConnection }
+
+procedure TPushServiceConnection.SetActive(AValue: Boolean);
+
+begin
+  if FActive=AValue then
+    Exit;
+  if Service.NeedsStart then
+    Service.StartService;
+  FActive:=AValue;
+end;
+
+procedure TPushServiceConnection.DoChange(aChanges: TPushService.TChanges);
+begin
+  if Active and Assigned(OnChange) then
+    OnChange(Self,aChanges);
+end;
+
+procedure TPushServiceConnection.DoReceiveNotification(const aNotification: TPushServiceNotification);
+begin
+  if Active and Assigned(OnReceiveNotification) then
+    OnReceiveNotification(Self,aNotification);
+end;
+
+constructor TPushServiceConnection.Create(const aService: TPushService);
+begin
+  FService:=aService;
+end;
+
+destructor TPushServiceConnection.Destroy;
+begin
+  if Assigned(FService) then
+    FService.RemoveConnection(Self);
+  inherited Destroy;
+end;
+
+procedure TPushServiceConnection.AfterConstruction;
+begin
+  inherited AfterConstruction;
+  if Assigned(FService) then
+    FService.AddConnection(Self);
+end;
+
+{ TPushServiceManager }
+
+function TPushServiceManager.GetCount: Integer;
+begin
+  Result:=FList.Count;
+end;
+
+function TPushServiceManager.GetService(aIndex: Integer): TPushService;
+begin
+  Result:=TServiceRegistration(FList.Items[aIndex]).Service;
+end;
+
+class constructor TPushServiceManager.Init;
+begin
+  _instance:=TPushServiceManager.Create;
+end;
+
+class destructor TPushServiceManager.done;
+begin
+  FreeAndNil(_instance);
+end;
+
+constructor TPushServiceManager.create;
+begin
+  FList:=TFPObjectList.Create(True);
+end;
+
+destructor TPushServiceManager.Destroy;
+
+var
+  I : integer;
+
+begin
+  For I:=0 to FList.Count-1 do
+    GetService(I).FManager:=nil;
+  FreeAndNil(FList);
+  inherited Destroy;
+end;
+
+
+procedure TPushServiceManager.AddService(aService: TPushService; aOwnsService: Boolean);
+begin
+  if IndexOfService(aService)<>-1 then
+    Raise EPushServiceError.CreateFmt(SErrServiceRegistered,[aService.ServiceName]);
+  FList.Add(TServiceRegistration.Create(aService,aOwnsService));
+  aService.FManager:=Self;
+end;
+
+procedure TPushServiceManager.RemoveService(aService: TPushService);
+
+var
+  Idx : Integer;
+
+begin
+  Idx:=IndexOfService(aService);
+  if Idx<0 then
+    Exit;
+  aService.FManager:=Nil;
+  TServiceRegistration(FList[Idx]).FService:=nil;
+  FList.Delete(Idx);
+end;
+
+function TPushServiceManager.GetServiceByName(const aServiceName: string): TPushService;
+
+var
+  Idx : integer;
+
+begin
+  Result:=nil;
+  Idx:=IndexOfServiceByName(aServiceName);
+  if Idx<>-1 then
+    Result:=GetService(Idx);
+end;
+
+function TPushServiceManager.IndexOfServiceByName(const aServiceName: string): Integer;
+begin
+  Result:=FList.Count-1;
+  While (Result>=0) and Not SameText(GetService(Result).ServiceName,aServiceName) do
+    Dec(Result);
+end;
+
+function TPushServiceManager.IndexOfService(const aService: TPushService): Integer;
+
+begin
+  Result:=FList.Count-1;
+  While (Result>=0) and (GetService(Result)<>aService) do
+    Dec(Result);
+end;
+
+{ TPushServiceManager.TServiceRegistration }
+
+constructor TPushServiceManager.TServiceRegistration.create(aService: TPushService; aOwned: Boolean);
+begin
+  FService:=aService;
+  FOwned:=aOwned;
+end;
+
+destructor TPushServiceManager.TServiceRegistration.Destroy;
+begin
+  if FOwned then
+    FreeAndNil(FService);
+  inherited Destroy;
+end;
+
+end.
+
+

+ 4 - 0
packages/vcl-compat/tests/testcompat.lpi

@@ -60,6 +60,10 @@
         <Filename Value="utcjson.pas"/>
         <Filename Value="utcjson.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
       </Unit>
       </Unit>
+      <Unit>
+        <Filename Value="utcpush.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
     </Units>
     </Units>
   </ProjectOptions>
   </ProjectOptions>
   <CompilerOptions>
   <CompilerOptions>

+ 1 - 1
packages/vcl-compat/tests/testcompat.lpr

@@ -5,7 +5,7 @@ program testcompat;
 uses
 uses
   {$IFDEF UNIX}cwstring,{$ENDIF}
   {$IFDEF UNIX}cwstring,{$ENDIF}
   Classes, consoletestrunner, tcnetencoding, tciotuils, 
   Classes, consoletestrunner, tcnetencoding, tciotuils, 
-  utmessagemanager, utcdevices, utcanalytics, utcimagelist, utcnotifications, utcjson;
+  utmessagemanager, utcdevices, utcanalytics, utcimagelist, utcnotifications, utcjson, utcpush;
 
 
 type
 type
 
 

+ 373 - 0
packages/vcl-compat/tests/utcpush.pas

@@ -0,0 +1,373 @@
+unit utcpush;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, fpcunit, testutils, testregistry, system.pushnotifications;
+
+type
+  
+  { TMyPushService }
+
+  TMyPushService = class(TPushService)
+  Private
+    FMyDeviceID : TPropArray;
+    FMyDeviceToken : TPropArray;
+    FMyStartError: String;
+    FMyStatus: TStatus;
+  protected
+    function GetDeviceID: TPropArray; override;
+    function GetDeviceToken: TPropArray; override;
+    function GetStartupError: string; override;
+    function GetStartupNotifications: TPushServiceNotificationArray; override;
+    function GetStatus: TStatus; override;
+    procedure StartService; override;
+    procedure StopService; override;
+  Public
+    constructor Create(const aOwner: TPushServiceManager; const aServiceName: string); override;
+    procedure Change(AChanges: TChanges);
+    procedure ReceiveNotification(const aNotification: TPushServiceNotification);
+    Property MyStatus : TStatus Read FMyStatus Write FMyStatus;
+    Property MyDeviceID : TPropArray Read FMyDeviceID Write FMyDeviceID;
+    Property MyDeviceToken : TPropArray Read FMyDeviceToken Write FMyDeviceToken;
+    Property MyStartError : String Read FMyStartError write FMyStartError;
+  end;
+
+  { TTestPushNotifications }
+
+  TTestPushNotifications= class(TTestCase)
+  private
+    FConn: TPushServiceConnection;
+    FConn2: TPushServiceConnection;
+    FManager: TPushServiceManager;
+    FMyService: TMyPushService;
+    FChanges: TPushService.TChanges;
+    FChangeCount : Integer;
+    FNotif: TPushServiceNotification;
+    FReceiveCount : Integer;
+    FReceived : TPushServiceNotification;
+    procedure DoChange(Sender: TObject; AChange: TPushService.TChanges);
+    procedure DoReceive(Sender: TObject; const aNotification: TPushServiceNotification);
+    function GetService: TPushService;
+  protected
+    procedure SetUp; override;
+    procedure TearDown; override;
+    Property Manager : TPushServiceManager Read FManager;
+    Property Service : TPushService Read GetService;
+    Property MyService : TMyPushService Read FMyService Write FMyService;
+    Property Conn : TPushServiceConnection Read FConn Write FConn;
+    Property Conn2 : TPushServiceConnection Read FConn2 Write FConn2;
+    Property Notif : TPushServiceNotification Read FNotif Write FNotif;
+  published
+    procedure TestHookUp;
+    Procedure TestCreateService;
+    Procedure TestAddService;
+    Procedure TestRemoveService;
+    Procedure TestIndexOfService;
+    Procedure TestIndexOfServiceName;
+    Procedure TestGetServiceByName;
+    Procedure TestCreateServiceConnection;
+    Procedure TestDestroyServiceConnection;
+    Procedure TestIndexOfConnection;
+    Procedure TestIndexOfConnection2;
+    Procedure TestConnectionActive;
+    Procedure TestServiceAppProp;
+    Procedure TestDeviceID;
+    Procedure TestDeviceToken;
+    Procedure TestStartupError;
+    Procedure TestOnChange;
+    Procedure TestOnChange2;
+    Procedure TestOnReceive;
+    Procedure TestOnReceive2;
+  end;
+
+implementation
+
+{ TMyPushService }
+
+function TMyPushService.GetDeviceID: TPropArray;
+begin
+  Result:=FMyDeviceID;
+end;
+
+function TMyPushService.GetDeviceToken: TPropArray;
+begin
+  Result:=FMyDeviceToken;
+end;
+
+function TMyPushService.GetStartupError: string;
+
+begin
+  Result:=FMyStartError;
+end;
+
+function TMyPushService.GetStartupNotifications: TPushServiceNotificationArray;
+
+begin
+  Result:=[];
+end;
+
+function TMyPushService.GetStatus: TStatus;
+
+begin
+  Result:=FMyStatus;
+end;
+
+procedure TMyPushService.StartService;
+
+begin
+  FMyStatus:=TStatus.Started;
+end;
+
+procedure TMyPushService.StopService;
+
+begin
+  FMyStatus:=TStatus.Stopped;
+end;
+
+constructor TMyPushService.Create(const aOwner: TPushServiceManager; const aServiceName: string);
+
+begin
+  inherited Create(aOwner, aServiceName);
+  FMyDeviceID:=[];
+  FMyDeviceToken:=[];
+  FMyStartError:='';
+  FMyStatus:=TStatus.Stopped;
+end;
+
+procedure TMyPushService.Change(AChanges: TChanges);
+begin
+  DoChange(aChanges);
+end;
+
+procedure TMyPushService.ReceiveNotification(const aNotification: TPushServiceNotification);
+begin
+  DoReceiveNotification(aNotification);
+
+end;
+
+procedure TTestPushNotifications.TestHookUp;
+
+begin
+  AssertNotNull('Have Global Instance',TPushServiceManager.Instance);
+  AssertNotNull('Have local Instance',Manager);
+  AssertEquals('No services',0, Manager.Count);
+  AssertEquals('No change count',0, FChangeCount);
+  AssertTrue('FChanges empty',[]=FChanges);
+
+end;
+
+procedure TTestPushNotifications.TestCreateService;
+begin
+  MyService:=TMyPushService.Create(Manager,'my');
+  AssertEquals('ServiceName','my',MyService.ServiceName);
+  AssertSame('Manager',Manager,MyService.Manager);
+  AssertEquals('Registered in Manager 1',1,Manager.Count);
+  AssertSame('Registered in Manager 2',Service,Manager.Services[0]);
+end;
+
+procedure TTestPushNotifications.TestAddService;
+begin
+  MyService:=TMyPushService.Create(Nil,'my');
+  AssertNull('Manager',MyService.Manager);
+  Manager.AddService(MyService,False);
+  AssertSame('Manager',Manager,MyService.Manager);
+  AssertEquals('Registered in Manager 1',1,Manager.Count);
+  AssertSame('Registered in Manager 2',Service,Manager.Services[0]);
+end;
+
+procedure TTestPushNotifications.TestRemoveService;
+begin
+  TestAddService;
+  Manager.RemoveService(MyService);
+  AssertEquals('No longer Registered in Manager ',0,Manager.Count);
+end;
+
+procedure TTestPushNotifications.TestIndexOfService;
+begin
+  MyService:=TMyPushService.Create(Manager,'my');
+  TMyPushService.Create(Manager,'my2');
+  AssertEquals('IndexOf',0,Manager.IndexOfService(MyService));
+end;
+
+procedure TTestPushNotifications.TestIndexOfServiceName;
+begin
+  MyService:=TMyPushService.Create(Manager,'my');
+  TMyPushService.Create(Manager,'my2');
+  AssertEquals('IndexOf',0,Manager.IndexOfServiceByName('my'));
+end;
+
+procedure TTestPushNotifications.TestGetServiceByName;
+begin
+  TestAddService;
+  AssertSame('Result', MyService, Manager.GetServiceByName('my'));
+
+end;
+
+procedure TTestPushNotifications.TestCreateServiceConnection;
+begin
+  MyService:=TMyPushService.Create(Manager,'my');
+  Conn:=MyService.CreateConnection;
+  AssertFalse('Not active',Conn.Active);
+  AssertEquals('Conn count',1,MyService.ConnectionCount);
+  AssertSame('Conn[0]',Conn,MyService.Connections[0]);
+end;
+
+procedure TTestPushNotifications.TestDestroyServiceConnection;
+begin
+  TestCreateServiceConnection;
+  FreeAndNil(FConn);
+  AssertEquals('Conn count',0,MyService.ConnectionCount);
+end;
+
+procedure TTestPushNotifications.TestIndexOfConnection;
+
+
+begin
+  TestCreateServiceConnection;
+  Conn2:=MyService.CreateConnection;
+  AssertEquals('IndexOf',0,Service.IndexOfConnection(Conn));
+
+end;
+
+procedure TTestPushNotifications.TestIndexOfConnection2;
+
+begin
+  TestCreateServiceConnection;
+  Conn2:=TPushServiceConnection.Create(Nil);
+  AssertEquals('IndexOf',-1,Service.IndexOfConnection(Conn2));
+end;
+
+procedure TTestPushNotifications.TestConnectionActive;
+begin
+  TestCreateServiceConnection;
+  Conn.Active:=True;
+  AssertTrue('Service running',TPushService.TStatus.Started=MyService.Status);
+end;
+
+procedure TTestPushNotifications.TestServiceAppProp;
+begin
+  MyService:=TMyPushService.Create(Nil,'my');
+  MyService.AppProps['a']:='b';
+  MyService.AppProps['b']:='c';
+  AssertEquals('Approps','b',MyService.AppProps['a']);
+end;
+
+procedure TTestPushNotifications.TestDeviceID;
+begin
+  MyService:=TMyPushService.Create(Nil,'my');
+  MyService.MyDeviceID:=[TPushService.TPropPair.Create('a','b'),TPushService.TPropPair.Create('b','c')];
+  AssertEquals('Device ID','c',MyService.DeviceIDValue['b']);
+end;
+
+procedure TTestPushNotifications.TestDeviceToken;
+begin
+  MyService:=TMyPushService.Create(Nil,'my');
+  MyService.MyDeviceToken:=[TPushService.TPropPair.Create('a','b'),TPushService.TPropPair.Create('b','c')];
+  AssertEquals('Device token','c',MyService.DeviceTokenValue['b']);
+end;
+
+procedure TTestPushNotifications.TestStartupError;
+begin
+  MyService:=TMyPushService.Create(Nil,'my');
+  MyService.MyStartError:='xyz';
+  AssertEquals('Service ','xyz',Service.StartupError);
+end;
+
+procedure TTestPushNotifications.TestOnChange;
+begin
+  TestCreateServiceConnection;
+  Conn.OnChange:=@DoChange;
+  Conn.Active:=True;
+  Conn2:=MyService.CreateConnection;
+  Conn2.OnChange:=@DoChange;
+  Conn2.Active:=True;
+  MyService.Change([TPushService.TChange.DeviceToken, TPushService.TChange.StartupNotifications]);
+  AssertEquals('Count',2,FChangeCount);
+  AssertTrue('Correct changes',[TPushService.TChange.DeviceToken, TPushService.TChange.StartupNotifications]=FChanges);
+
+end;
+
+procedure TTestPushNotifications.TestOnChange2;
+begin
+  // only active connections need to get the event
+  TestCreateServiceConnection;
+  Conn.OnChange:=@DoChange;
+  Conn2:=MyService.CreateConnection;
+  Conn2.OnChange:=@DoChange;
+  MyService.Change([TPushService.TChange.DeviceToken, TPushService.TChange.StartupNotifications]);
+  AssertEquals('Count',0,FChangeCount);
+  AssertTrue('Correct changes',[]=FChanges);
+end;
+
+procedure TTestPushNotifications.TestOnReceive;
+begin
+  // only active connections need to get the event
+  TestCreateServiceConnection;
+  Conn.OnReceiveNotification:=@DoReceive;
+  Conn.Active:=True;
+  Conn2:=MyService.CreateConnection;
+  Conn2.OnReceiveNotification:=@DoReceive;
+  Conn2.Active:=True;
+  Notif:=TPushServiceNotification.Create;
+  MyService.ReceiveNotification(Notif);
+  AssertEquals('Count',2,FReceiveCount);
+  AssertSame('Correct changes',Notif,FReceived);
+end;
+
+procedure TTestPushNotifications.TestOnReceive2;
+begin
+  TestCreateServiceConnection;
+  Conn.OnReceiveNotification:=@DoReceive;
+  Conn2:=MyService.CreateConnection;
+  Conn2.OnReceiveNotification:=@DoReceive;
+  Notif:=TPushServiceNotification.Create;
+  MyService.ReceiveNotification(Notif);
+  AssertEquals('Count',0,FReceiveCount);
+end;
+
+function TTestPushNotifications.GetService: TPushService;
+
+begin
+  Result:=FMyService;
+end;
+
+procedure TTestPushNotifications.DoChange(Sender: TObject; AChange: TPushService.TChanges);
+begin
+  FChanges:=aChange;
+  Inc(FChangeCount);
+end;
+
+procedure TTestPushNotifications.DoReceive(Sender: TObject; const aNotification: TPushServiceNotification);
+begin
+  FReceived:=aNotification;
+  inc(FReceiveCount);
+end;
+
+procedure TTestPushNotifications.SetUp;
+
+begin
+  FManager:=TPushServiceManager.Create;
+  FChanges:=[];
+  FChangeCount:=0;
+  FReceiveCount:=0;
+  FReceived:=Nil;
+end;
+
+procedure TTestPushNotifications.TearDown;
+
+begin
+  FreeAndNil(FNotif);
+  FreeAndNil(FConn);
+  FreeAndNil(FConn2);
+  FreeAndNil(FMyService);
+  FreeAndNil(FManager);
+end;
+
+initialization
+
+  RegisterTest(TTestPushNotifications);
+end.
+