Você pode usar o SDK do Driver para oferecer navegação e rastreamento aprimorados ao seu aplicativo de viagem e andamento do pedido. O SDK do Driver fornece atualizações de local e tarefas de veículos ao mecanismo de frota da solução de viagens e entregas sob demanda.
O SDK do Driver mantém os serviços do Fleet Engine e seus serviços personalizados cientes da
localização e do estado do veículo. Por exemplo, o veículo pode ser ONLINE
ou OFFLINE
, e a localização dele muda conforme a viagem progride.
Requisitos mínimos do sistema
Pré-requisitos
Este guia pressupõe que seu app já implementa o SDK de navegação e que o back-end do Fleet Engine está configurado e disponível. No entanto, o código de exemplo apresenta uma amostra de como configurar o SDK do Navigation.
Também é necessário ativar o SDK do Maps para iOS no projeto do Google Cloud e Gerar uma chave de API.
Configuração do projeto
É possível configurar o SDK do driver usando o CocoaPods.
CocoaPods
Para configurar o SDK do driver usando o CocoaPods, você precisa dos seguintes itens:
- A ferramenta CocoaPods: para instalar esta ferramenta, abra o terminal e execute o comando a seguir.
sudo gem install cocoapods
Consulte o Guia de primeiros passos do CocoaPods para mais detalhes.
- Crie um Podfile para o SDK do driver e use-o para instalar a API e as dependências dela. Crie um arquivo chamado Podfile no diretório do projeto. Esse arquivo define as dependências do seu projeto. Edite o Podfile e adicione as dependências. Veja um exemplo que inclui as dependências:
source "https://github.com/CocoaPods/Specs.git"
target 'YOUR_APPLICATION_TARGET_NAME_HERE' do
pod 'GoogleRidesharingDriver'
end
Aqui está um exemplo que inclui os pods Alfa e Beta do SDK do Driver como dependências:
source "https://cpdc-eap.googlesource.com/ridesharing-driver-sdk.git"
source "https://github.com/CocoaPods/Specs.git"
target 'YOUR_APPLICATION_TARGET_NAME_HERE' do
pod 'GoogleRidesharingDriver'
end
- Salve o Podfile. Abra um terminal e acesse o diretório que contém o Podfile:
cd <path-to-project>
- Execute o comando de instalação do pod. Isso instalará as APIs especificadas no Podfile com as respectivas dependências.
pod install
- Feche o Xcode e clique duas vezes no arquivo .xcworkspace do seu projeto para iniciar o Xcode. A partir desse momento, você precisará usar o arquivo .xcworkspace para abrir o projeto.
Instalação manual
Um XCFramework é um pacote binário usado para instalar o SDK do Driver. É possível usar esse pacote em várias plataformas, incluindo máquinas que usam o Apple Silicon. Este guia mostra como adicionar manualmente o XCFramework que contém o SDK do Driver ao seu projeto e definir as configurações de build no Xcode.
Faça o download do binário e dos recursos do SDK:
Descompacte os arquivos compactados para acessar o XCFramework e os recursos.
Inicie o Xcode e abra um projeto existente ou crie um novo. Se você não tem experiência com o iOS, crie um novo projeto e selecione o modelo de app iOS.
Crie um grupo do Frameworks no seu grupo de projeto, caso ainda não tenha um.
Arraste o arquivo
gRPCCertificates.bundle
para o diretório de nível superior do seu projeto Xcode. Quando solicitado, selecione "Copiar itens", se necessário.Para instalar o SDK do driver, arraste o arquivo
GoogleRidesharingDriver.xcframework
para o projeto em Frameworks, bibliotecas e conteúdo incorporado. Quando solicitado, selecione Copiar itens, se necessário.Arraste o
GoogleRidesharingDriver.bundle
salvo para o diretório de nível superior do seu projeto Xcode. Quando solicitado, selecioneCopy items if needed
.Selecione o projeto no Navegador de projetos e escolha o alvo do aplicativo.
Abra a guia "Fases de build" e, em "Vincular binário a bibliotecas", adicione os frameworks e bibliotecas a seguir, se ainda não estiverem presentes:
Accelerate.framework
AudioToolbox.framework
AVFoundation.framework
CoreData.framework
CoreGraphics.framework
CoreLocation.framework
CoreTelephony.framework
CoreText.framework
GLKit.framework
ImageIO.framework
libc++.tbd
libxml2.tbd
libz.tbd
LocalAuthentication.framework
OpenGLES.framework
QuartzCore.framework
SystemConfiguration.framework
UIKit.framework
WebKit.framework
Escolha seu projeto, em vez de um destino específico, e abra a guia Build Settings. Na seção Outras sinalizações do vinculador, adicione
-ObjC
para depuração e lançamento. Se essas configurações não estiverem visíveis, mude o filtro na barra "Build Settings" de Basic para All.
Versões Alfa/Beta do SDK
Para configurar as versões Alfa ou Beta do SDK do Driver para iOS, você precisa dos seguintes itens:
- A ferramenta CocoaPods: para instalar esta ferramenta, abra o terminal e execute o comando a seguir.
sudo gem install cocoapods
Consulte o Guia de primeiros passos do CocoaPods para mais detalhes.
- Sua conta de desenvolvimento na lista de acesso do Google. O repositório de pods das versões Alfa e Beta do SDK não é de fonte pública. Para acessá-las, entre em contato com o Engenheiro de clientes do Google. O engenheiro adiciona sua conta de desenvolvimento à lista de acesso e, em seguida, define um cookie para autenticação.
Depois que o projeto estiver na lista de acesso, será possível acessar o pod.
- Crie um Podfile para o SDK de entrega para iOS e use-o para instalar a API e as dependências dela. Crie um arquivo chamado Podfile no diretório do projeto. Esse arquivo define as dependências do seu projeto. Edite o Podfile e adicione suas dependências. Veja um exemplo que inclui as dependências:
source "https://cpdc-eap.googlesource.com/ridesharing-driver-sdk.git"
source "https://github.com/CocoaPods/Specs.git"
target 'YOUR_APPLICATION_TARGET_NAME_HERE' do
pod 'GoogleRidesharingDriver'
end
- Salve o Podfile. Abra um terminal e acesse o diretório que contém o Podfile:
cd <path-to-project>
- Execute o comando de instalação do pod. Esse comando instala as APIs especificadas no Podfile com as respectivas dependências.
pod install
- Feche o Xcode e clique duas vezes no arquivo .xcworkspace do seu projeto para iniciar o Xcode. A partir desse momento, você precisará usar o arquivo .xcworkspace para abrir o projeto.
Implementar autorização e autenticação
Quando o app do driver gera e envia atualizações para o back-end do Fleet Engine,
as solicitações precisam incluir tokens de acesso válidos. Para autorizar e autenticar
essas solicitações, o SDK do driver chama seu objeto
em conformidade com o protocolo GMTDAuthorization
. O objeto é responsável por
fornecer o token de acesso necessário.
Como desenvolvedor do app, você escolhe como os tokens são gerados. Sua implementação precisa oferecer a capacidade de fazer o seguinte:
- Busque um token de acesso, possivelmente no formato JSON, de um servidor HTTPS.
- analisar e armazenar em cache o token;
- Atualizar o token quando ele expirar.
Para detalhes sobre os tokens esperados pelo servidor do Fleet Engine, consulte Como criar um JSON Web Token (JWT) para autorização.
O ID do provedor é o mesmo que o ID do projeto do Google Cloud. Consulte o Guia de início rápido do Fleet Engine para mais informações.
O exemplo a seguir implementa um provedor de token de acesso:
Swift
import GoogleRidesharingDriver
private let providerURL = "INSERT_YOUR_TOKEN_PROVIDER_URL"
class SampleAccessTokenProvider: NSObject, GMTDAuthorization {
private struct AuthToken {
// The cached vehicle token.
let token: String
// Keep track of when the token expires for caching.
let expiration: TimeInterval
// Keep track of the vehicle ID the cached token is for.
let vehicleID: String
}
enum AccessTokenError: Error {
case missingAuthorizationContext
case missingData
}
private var authToken: AuthToken?
func fetchToken(
with authorizationContext: GMTDAuthorizationContext?,
completion: @escaping GMTDAuthTokenFetchCompletionHandler
) {
// Get the vehicle ID from the authorizationContext. This is set by the Driver SDK.
guard let authorizationContext = authorizationContext else {
completion(nil, AccessTokenError.missingAuthorizationContext)
return
}
let vehicleID = authorizationContext.vehicleID
// If appropriate, use the cached token.
if let authToken = authToken,
authToken.expiration > Date.now.timeIntervalSince1970 && authToken.vehicleID == vehicleID
{
completion(authToken.token, nil)
return
}
// Otherwise, try to fetch a new token from your server.
let request = URLRequest(url: URL(string: providerURL))
let task = URLSession.shared.dataTask(with: request) { [weak self] data, _, error in
guard let strongSelf = self else { return }
guard error == nil else {
completion(nil, error)
return
}
// Replace the following key values with the appropriate keys based on your
// server's expected response.
let vehicleTokenKey = "VEHICLE_TOKEN_KEY"
let tokenExpirationKey = "TOKEN_EXPIRATION"
guard let data = data,
let fetchData = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let token = fetchData[vehicleTokenKey] as? String,
let expiration = fetchData[tokenExpirationKey] as? Double
else {
completion(nil, AccessTokenError.missingData)
return
}
strongSelf.authToken = AuthToken(
token: token, expiration: expiration, vehicleID: vehicleID)
completion(token, nil)
}
task.resume()
}
}
Objective-C
#import "SampleAccessTokenProvider.h"
#import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
// SampleAccessTokenProvider.h
@interface SampleAccessTokenProvider : NSObject<GMTDAuthorization>
@end
static NSString *const PROVIDER_URL = @"INSERT_YOUR_TOKEN_PROVIDER_URL";
// SampleAccessTokenProvider.m
@implementation SampleAccessTokenProvider{
// The cached vehicle token.
NSString *_cachedVehicleToken;
// Keep track of the vehicle ID the cached token is for.
NSString *_lastKnownVehicleID;
// Keep track of when tokens expire for caching.
NSTimeInterval _tokenExpiration;
}
- (void)fetchTokenWithContext:(nullable GMTDAuthorizationContext *)authorizationContext
completion:(nonnull GMTDAuthTokenFetchCompletionHandler)completion {
// Get the vehicle ID from the authorizationContext. This is set by the Driver SDK.
NSString *vehicleID = authorizationContext.vehicleID;
if (!vehicleID) {
NSAssert(NO, @"Vehicle ID is missing from authorizationContext.");
return;
}
// Clear cached vehicle token if vehicle ID has changed.
if (![_lastKnownVehicleID isEqual:vehicleID]) {
_tokenExpiration = 0.0;
_cachedVehicleToken = nil;
}
_lastKnownVehicleID = vehicleID;
// Clear cached vehicletoken if it has expired.
if ([[NSDate date] timeIntervalSince1970] > _tokenExpiration) {
_cachedVehicleToken = nil;
}
// If appropriate, use the cached token.
if (_cachedVehicleToken) {
completion(_cachedVehicleToken, nil);
return;
}
// Otherwise, try to fetch a new token from your server.
NSURL *requestURL = [NSURL URLWithString:PROVIDER_URL];
NSMutableURLRequest *request =
[[NSMutableURLRequest alloc] initWithURL:requestURL];
request.HTTPMethod = @"GET";
// Replace the following key values with the appropriate keys based on your
// server's expected response.
NSString *vehicleTokenKey = @"VEHICLE_TOKEN_KEY";
NSString *tokenExpirationKey = @"TOKEN_EXPIRATION";
__weak typeof(self) weakSelf = self;
void (^handler)(NSData *_Nullable data, NSURLResponse *_Nullable response,
NSError *_Nullable error) =
^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) {
typeof(self) strongSelf = weakSelf;
if (error) {
completion(nil, error);
return;
}
NSError *JSONError;
NSMutableDictionary *JSONResponse =
[NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&JSONError];
if (JSONError) {
completion(nil, JSONError);
return;
} else {
// Sample code only. No validation logic.
id expirationData = JSONResponse[tokenExpirationKey];
if ([expirationData isKindOfClass:[NSNumber class]]) {
NSTimeInterval expirationTime = ((NSNumber *)expirationData).doubleValue;
strongSelf->_tokenExpiration = [[NSDate date] timeIntervalSince1970] + expirationTime;
}
strongSelf->_cachedVehicleToken = JSONResponse[vehicleTokenKey];
completion(JSONResponse[vehicleTokenKey], nil);
}
};
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *mainQueueURLSession =
[NSURLSession sessionWithConfiguration:config delegate:nil
delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *task = [mainQueueURLSession dataTaskWithRequest:request completionHandler:handler];
[task resume];
}
@end
Criar uma instância da API RidesharingDriverAPI
Para ter uma instância do GMTDVehicleReporter
, primeiro você precisa criar uma
instância do GMTDRidesharingDriverAPI
usando o providerID, o ID do veículo, o driverContext e o accessTokenProvider. O providerID é igual ao ID do projeto do Google Cloud. E você pode acessar a instância GMTDVehicleReporter
diretamente da
API do driver.
O exemplo a seguir cria uma instância GMTDRidesharingDriverAPI
:
Swift
import GoogleRidesharingDriver
private let providerID = "INSERT_YOUR_PROVIDER_ID"
class SampleViewController: UIViewController {
private let mapView: GMSMapView
override func viewDidLoad() {
super.viewDidLoad()
let vehicleID = "INSERT_CREATED_VEHICLE_ID"
let accessTokenProvider = SampleAccessTokenProvider()
let driverContext = GMTDDriverContext(
accessTokenProvider: accessTokenProvider,
providerID: providerID,
vehicleID: vehicleID,
navigator: mapView.navigator)
let ridesharingDriverAPI = GMTDRidesharingDriverAPI(driverContext: driverContext)
}
}
Objective-C
#import "SampleViewController.h"
#import "SampleAccessTokenProvider.h"
#import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
static NSString *const PROVIDER_ID = @"INSERT_YOUR_PROVIDER_ID";
@implementation SampleViewController {
GMSMapView *_mapView;
}
- (void)viewDidLoad {
NSString *vehicleID = @"INSERT_CREATED_VEHICLE_ID";
SampleAccessTokenProvider *accessTokenProvider =
[[SampleAccessTokenProvider alloc] init];
GMTDDriverContext *driverContext =
[[GMTDDriverContext alloc] initWithAccessTokenProvider:accessTokenProvider
providerID:PROVIDER_ID
vehicleID:vehicleID
navigator:_mapView.navigator];
GMTDRidesharingDriverAPI *ridesharingDriverAPI = [[GMTDRidesharingDriverAPI alloc] initWithDriverContext:driverContext];
}
Como opção, detectar eventos do VehicleReporter
O GMTDVehicleReporter
atualiza o veículo periodicamente quando locationTrackingEnabled
é true
. Para responder a essas atualizações periódicas, qualquer
objeto pode se inscrever em eventos GMTDVehicleReporter
seguindo o
protocolo GMTDVehicleReporterListener
.
É possível processar os seguintes eventos:
vehicleReporter(_:didSucceed:)
Informa ao app do motorista que os serviços de back-end receberam a atualização do local e do estado do veículo.
vehicleReporter(_:didFail:withError:)
Informa ao listener que uma atualização de veículo falhou. Enquanto o rastreamento de localização estiver ativado, o
GMTDVehicleReporter
continuará enviando os dados mais recentes para o back-end do Fleet Engine.
O exemplo a seguir lida com esses eventos:
Swift
import GoogleRidesharingDriver
private let providerID = "INSERT_YOUR_PROVIDER_ID"
class SampleViewController: UIViewController, GMTDVehicleReporterListener {
private let mapView: GMSMapView
override func viewDidLoad() {
// Assumes you have implemented the sample code up to this step.
ridesharingDriverAPI.vehicleReporter.add(self)
}
func vehicleReporter(_ vehicleReporter: GMTDVehicleReporter, didSucceed vehicleUpdate: GMTDVehicleUpdate) {
// Handle update succeeded.
}
func vehicleReporter(_ vehicleReporter: GMTDVehicleReporter, didFail vehicleUpdate: GMTDVehicleUpdate, withError error: Error) {
// Handle update failed.
}
}
Objective-C
/*
* SampleViewController.h
*/
@interface SampleViewController : UIViewController<GMTDVehicleReporterListener>
@end
/*
* SampleViewController.m
*/
#import "SampleViewController.h"
#import "SampleAccessTokenProvider.h"
#import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
static NSString *const PROVIDER_ID = @"INSERT_YOUR_PROVIDER_ID";
@implementation SampleViewController {
GMSMapView *_mapView;
}
- (void)viewDidLoad {
// Assumes you have implemented the sample code up to this step.
[ridesharingDriverAPI.vehicleReporter addListener:self];
}
- (void)vehicleReporter:(GMTDVehicleReporter *)vehicleReporter didSucceedVehicleUpdate:(GMTDVehicleUpdate *)vehicleUpdate {
// Handle update succeeded.
}
- (void)vehicleReporter:(GMTDVehicleReporter *)vehicleReporter didFailVehicleUpdate:(GMTDVehicleUpdate *)vehicleUpdate withError:(NSError *)error {
// Handle update failed.
}
@end
Adiciona GMTDVehicleReporter como um listener do GMSRoadSnappedLocationProvider
Para fornecer atualizações de localização ao SDK do Driver, o GMTDVehicleReporter
precisa ser definido como um listener do GMSRoadSnappedLocationProvider
.
Swift
import GoogleRidesharingDriver
private let providerID = "INSERT_YOUR_PROVIDER_ID"
class SampleViewController: UIViewController, GMTDVehicleReporterListener {
private let mapView: GMSMapView
override func viewDidLoad() {
// Assumes you have implemented the sample code up to this step.
if let roadSnappedLocationProvider = mapView.roadSnappedLocationProvider {
roadSnappedLocationProvider.add(ridesharingDriverAPI.vehicleReporter)
roadSnappedLocationProvider.startUpdatingLocation()
}
}
}
Objective-C
/*
* SampleViewController.h
*/
@interface SampleViewController : UIViewController<GMTDVehicleReporterListener>
@end
/*
* SampleViewController.m
*/
#import "SampleViewController.h"
#import "SampleAccessTokenProvider.h"
#import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
static NSString *const PROVIDER_ID = @"INSERT_YOUR_PROVIDER_ID";
@implementation SampleViewController {
GMSMapView *_mapView;
}
- (void)viewDidLoad {
// Assumes you have implemented the sample code up to this step.
[_mapView.roadSnappedLocationProvider addListener:ridesharingDriverAPI.vehicleReporter];
[_mapView.roadSnappedLocationProvider startUpdatingLocation];
}
@end
Ativar rastreamento de localização
Para ativar o rastreamento de localização, seu app pode definir locationTrackingEnabled
como true
em GMTDVehicleReporter
. O GMTDVehicleReporter
envia atualizações de local automaticamente. Depois que os serviços são correspondentes e atribuem o veículo a uma viagem, o GMTDVehicleReporter
envia atualizações de trajeto automaticamente quando GMSNavigator
está no modo de navegação (quando um destino é definido pelo setDestinations
).
O trajeto definido durante a atualização será o mesmo que o motorista estiver seguindo durante a sessão de navegação. Assim, para que o compartilhamento de jornada funcione
corretamente, o waypoint definido por setDestinations
precisa corresponder ao
destino definido no back-end do Fleet Engine.
Se locationTrackingEnabled
for definido como true
, as atualizações de viagens e veículos serão enviadas ao back-end do Fleet Engine em um intervalo regular com base no valor definido para locationUpdateInterval
. Se locationTrackingEnabled
for definido como false
,
as atualizações serão interrompidas e uma solicitação final de atualização do veículo será enviada ao back-end do Fleet Engine
para definir o estado do veículo como GMTDVehicleState.offline
. Consulte
updateVehicleState
para considerações especiais sobre como lidar com falhas quando locationTrackingEnabled
está definido como false
.
O exemplo a seguir ativa o rastreamento de localização:
Swift
import GoogleRidesharingDriver
private let providerID = "INSERT_YOUR_PROVIDER_ID"
class SampleViewController: UIViewController, GMTDVehicleReporterListener {
private let mapView: GMSMapView
override func viewDidLoad() {
// Assumes you have implemented the sample code up to this step.
ridesharingDriverAPI.vehicleReporter.locationTrackingEnabled = true
}
}
Objective-C
/*
* SampleViewController.m
*/
#import "SampleViewController.h"
#import "SampleAccessTokenProvider.h"
#import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
static NSString *const PROVIDER_ID = @"INSERT_YOUR_PROVIDER_ID";
@implementation SampleViewController {
GMSMapView *_mapView;
}
- (void)viewDidLoad {
// Assumes you have implemented the sample code up to this step.
ridesharingDriverAPI.vehicleReporter.locationTrackingEnabled = YES;
}
@end
Por padrão, o intervalo do relatório é de 10 segundos, mas pode ser alterado com locationUpdateInterval
. O intervalo mínimo de atualização permitido
é de cinco segundos. O intervalo máximo de atualização permitido é de 60 segundos. Atualizações
mais frequentes podem resultar em solicitações mais lentas e erros.
Atualizar o estado do veículo
O exemplo abaixo mostra como definir o estado do veículo como ONLINE
. Consulte
updateVehicleState
para ver mais detalhes.
Swift
import GoogleRidesharingDriver
private let providerID = "INSERT_YOUR_PROVIDER_ID"
class SampleViewController: UIViewController, GMTDVehicleReporterListener {
private let mapView: GMSMapView
override func viewDidLoad() {
// Assumes you have implemented the sample code up to this step.
ridesharingDriverAPI.vehicleReporter.update(.online)
}
}
Objective-C
#import "SampleViewController.h"
#import "SampleAccessTokenProvider.h"
#import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
static NSString *const PROVIDER_ID = @"INSERT_YOUR_PROVIDER_ID";
@implementation SampleViewController {
GMSMapView *_mapView;
}
- (void)viewDidLoad {
// Assumes you have implemented the sample code up to this step.
[ridesharingDriverAPI.vehicleReporter
updateVehicleState:GMTDVehicleStateOnline];
}
@end
Um erro update_mask
pode ocorrer quando a máscara está vazia e normalmente ocorre
na primeira atualização após a inicialização. O exemplo a seguir mostra como processar
esse erro:
Swift
import GoogleRidesharingDriver
class VehicleReporterListener: NSObject, GMTDVehicleReporterListener {
func vehicleReporter(
_ vehicleReporter: GMTDVehicleReporter,
didFail vehicleUpdate: GMTDVehicleUpdate,
withError error: Error
) {
let fullError = error as NSError
if let innerError = fullError.userInfo[NSUnderlyingErrorKey] as? NSError {
let innerFullError = innerError as NSError
if innerFullError.localizedDescription.contains("update_mask cannot be empty") {
emptyMaskUpdates += 1
return
}
}
failedUpdates += 1
}
override init() {
emptyMaskUpdates = 0
failedUpdates = 0
}
}
Objective-C
#import "VehicleReporterListener.h"
#import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
@implementation VehicleReporterListener {
NSInteger emptyMaskUpdates = 0;
NSInteger failedUpdates = 0;
}
- (void)vehicleReporter:(GMTDVehicleReporter *)vehicleReporter
didFailVehicleUpdate:(GMTDVehicleUpdate *)vehicleUpdate
withError:(NSError *)error {
for (NSError *underlyingError in error.underlyingErrors) {
if ([underlyingError.localizedDescription containsString:@"update_mask cannot be empty"]) {
emptyMaskUpdates += 1;
return;
}
}
failedUpdates += 1
}
@end
Desativar atualizações de localização e deixar o veículo off-line
O app pode desativar as atualizações e deixar o veículo off-line. Por exemplo, quando o
horário do motorista termina, o app pode definir locationTrackingEnabled
como false
.
A desativação das atualizações também define o status do veículo como OFFLINE
no back-end do
Fleet Engine.
Swift
vehicleReporter.locationTrackingEnabled = false
Objective-C
_vehicleReporter.locationTrackingEnabled = NO;