# Customization

## Custom Navigation Page

This section provides a quick integration guide for Embedded NavigationView

**Step 1** - Add _NavigationView_ to layout XML Add inside a Codeblock:

```xml
<ai.nextbillion.navigation.ui.NavigationView
    android:id="@+id/navigation_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:navigationLightTheme="@style/NavigationViewLight"
    app:navigationDarkTheme="@style/NavigationViewDark" />
```

**Note:** Apply your app theme overrides as needed.

**Step 2** - Forward Activity/Fragment lifecycle callbacks to _NavigationView_.

**Step 3** - Initialize _NavigationView_ before calling _startNavigation_. Add inside a Codeblock:

```java
navigationView.initialize(new OnNavigationReadyCallback() {
    @Override
    public void onNavigationReady(boolean isRunning) {
        // fetch route and prepare NavViewConfig
    }
});
```

**Note**: Call _startNavigation_ only after _onNavigationReady_.

**Step 4 -** Build _NavViewConfig_ with required fields, then add optional listeners. Add inside a Codeblock:

```java
NavViewConfig navViewConfig = NavViewConfig.builder()
    .route(route)
    .navigationListener(navigationListener)
    .routeListener(routeListener)
    .shouldSimulateRoute(true)
    .build(); 
```

**Step 5 -** Start embedded navigation. Add inside a Codeblock:

```java
navigationView.startNavigation(navViewConfig);
```

**Recommended order:** Initialize SDK in Application -> request runtime permissions -> fetch route -> initialize _NavigationView_ -> _startNavigation_.

For customizations, choose either drop-in navigation (_NavigationLauncher_ + _NavLauncherConfig_) or embedded _NavigationView_ for deeper UI and behavior control.

(In this article, we are not going to cover permission requesting and handling, the official guide can be found [here](https://developer.android.com/training/location/permissions))

## NavigationView

NavigationView is the key component of the navigation, we can either add it to the layout XML file or add it to ViewGroup programmatically

```xml
<ai.nextbillion.navigation.ui.NavigationView
    android:id="@+id/navigation_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
```

The code above is an example of how to add the NavigationView component to an XML layout file. The NavigationView class is a custom view class provided by Nextbillion.AI Navigation SDK, it is responsible for displaying the navigation map and all related information, such as the route, user location, and instructions. The NavigationView can be added to any layout file by using the _<ai.nextbillion.navigation.ui.NavigationView\>_ tag and setting the width and height to match the parent container. It is important to note that the id of the NavigationView is set to `@+id/navigation_view` this will be used to reference the view in the Java code.

### Lifecycle Binding

In order to ensure proper initialization and resource management, as well as handling user interactions, the _NavigationView_ component must be bound to the activity's lifecycle using a series of callbacks. This is achieved by binding the _NavigationView's_ lifecycle callbacks to the corresponding callbacks in the activity. This allows the _NavigationView_ to properly handle the activity's lifecycle transitions and respond accordingly. Following are the callbacks included:

| 1 | onCreate |
| --- | --- |
| 2 | onStart |
| 3 | onResume |
| 4 | onPause |
| 5 | onStop |
| 6 | onDestroy |
| 7 | onLowMemory |
| 8 | onBackPressed |
| 9 | onSaveInstanceState |
| 10 | onRestoreInstanceState |

```java
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
...
navigationView = findViewById(R.id.navigationView);
navigationView.onCreate(savedInstanceState);
...
}

    @Override
    public void onStart() {
        super.onStart();
        navigationView.onStart();
    }

    @Override
    public void onResume() {
        super.onResume();
        navigationView.onResume();
    }

    @Override
    public void onLowMemory() {
      super.onLowMemory();
      navigationView.onLowMemory();
    }

    @Override
    public void onBackPressed() {
      // If the navigation view didn't need to do anything, call super
      if (!navigationView.onBackPressed()) {
        super.onBackPressed();
      }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        navigationView.onSaveInstanceState(outState);
        super.onSaveInstanceState(outState);
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        navigationView.onRestoreInstanceState(savedInstanceState);
    }

    @Override
    public void onPause() {
        super.onPause();
        navigationView.onPause();
    }

    @Override
    public void onStop() {
        super.onStop();
        navigationView.onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        navigationView.onDestroy();
    }
```

By calling these methods on the _NavigationView_ in the corresponding activity lifecycle methods, the developer can ensure that the _NavigationView_ is properly set up and taken down, and that it responds to user interactions in a way that is consistent with the activity's state.

## Initialization

The _NavigationView_ component must be fully initialized before starting navigation. To ensure this, the `initialize` method can be used with an _OnNavigationReadyCallback_ to perform any necessary actions, such as route fetching or starting a navigation, once the _NavigationView_ is ready. The _onNavigationReady_ method within the callback will be triggered once the _NavigationView_ is fully initialized and ready to be used, and it will also indicate if a navigation is already running or not. Before we start navigation, we need to make sure that the _NavigationView_ is completely initialized, it's recommended to perform `startNavigation` in an _OnNavigationReadyCallback_,

```java
navigationView.initialize(new OnNavigationReadyCallback() {
@Override
public void onNavigationReady(boolean isRunning) {
//Perform route fetching or start a navigation here
}
});
```

## Start a Navigation

To start a navigation session in the _NavigationView_, we need to provide it with a _NavViewConfig_ that contains a _DirectionsRoute_. The following example shows how to create a _NavViewConfig_ and start a navigation:

```java
NavViewConfig.Builder config =
NavViewConfig.builder().route(directionsRoute);
navigationView.startNavigation(config.build());
```

_NavViewConfig_ customizes behavior and listeners. Start with minimal config, then add listeners only when needed.

for more details, please refer to [Android Navigation SDK Configurations](/docs/navigation/sdk/android/sdk-configuration-and-integration)

## Set Theme

_NavigationView_ supports light/dark base themes; extend them with custom styles and apply via layout attributes.

```xml
<ai.nextbillion.navigation.ui.NavigationView
  android:id='@+id/navigationView'
  android:layout_width='match_parent'
  android:layout_height='match_parent'
  app:navigationDarkTheme='@style/NavigationViewDark'
  app:navigationLightTheme='@style/NavigationViewLight'
/>
```

The Navigation SDK has provided two default styles

1.  **NavigationViewDark**
    
2.  **NavigationViewLight**
    

to customize them, we can declare our own styles by extending and modifying the default styles.

```xml
<style name='CustomNavigationViewLight' parent='NavigationViewLight'>
  ...
</style>

<style name='CustomNavigationViewDark' parent='NavigationViewDark'>
  ...
</style>
```

and update the layout file:

```xml
<ai.nextbillion.navigation.ui.NavigationView
  android:id='@+id/navigationView'
  android:layout_width='match_parent'
  android:layout_height='match_parent'
  app:navigationDarkTheme='@style/CustomNavigationViewDark'
  app:navigationLightTheme='@style/CustomNavigationViewLight'
/>
```

## Custom Navigation Notification

The Navigation SDK shows an ongoing notification during navigation. You can replace the default notification by implementing _NavigationNotification_ and passing it through _NavEngineConfig_.

```java
public interface NavigationNotification {

    /**
     * A Notification to start NavigationService as a foreground service
     */
    Notification getNotification();

    /**
     * An integer id that will be used to start this notification
     */
    int getNotificationId();

    /**
     * This method will be called every time the NavProgress is updated
     */
    void updateNotification(NavProgress routeProgress);

    /**
     * Will be triggered when {@link NextbillionNav#stopNavigation()} is called.
     */
    void onNavigationStopped(Context context);
}
```

### Step 1

Implements **NavigationNotification**

```java
public class CustomNavigationNotification implements NavigationNotification {

  public CustomNavigationNotification(Context applicationContext) {

  }

  @Override
  public Notification getNotification() {
    return null;
  }

  @Override
  public int getNotificationId() {
    return 0;
  }

  @Override
  public void updateNotification(NavProgress routeProgress) {
 
  }

  @Override
  public void onNavigationStopped(Context context) {
 
  }

}
```

### Step 2

Build notification view

```java
public class CustomNavigationNotification implements NavigationNotification {

  private static final int CUSTOM_NOTIFICATION_ID = 11112;
  private static final String STOP_NAVIGATION_ACTION = "stop_navigation_action";

  private final Notification customNotification;
  private final NotificationCompat.Builder customNotificationBuilder;
  private final NotificationManager notificationManager;

  public CustomNavigationNotification(Context applicationContext) {
    notificationManager = (NotificationManager) applicationContext.getSystemService(Context.NOTIFICATION_SERVICE);

    customNotificationBuilder = new NotificationCompat.Builder(applicationContext, NAVIGATION_NOTIFICATION_CHANNEL)
      .setSmallIcon(R.drawable.ic_navigation)
      .setContentTitle("Customised Notification")
      .setContentText("Ongoing Navigation!");

    customNotification = customNotificationBuilder.build();
  }

  @Override
  public Notification getNotification() {
    return customNotification;
  }

  @Override
  public int getNotificationId() {
    return CUSTOM_NOTIFICATION_ID;
  }

  @Override
  public void updateNotification(NavProgress routeProgress) {
   
  }

  @Override
  public void onNavigationStopped(Context context) {   
    notificationManager.cancel(CUSTOM_NOTIFICATION_ID);
  }
}
```

To display the notification, we need to finish step 3 as well.

### Step 3

Notification update

```java
public class CustomNavigationNotification implements NavigationNotification {

...

  private int numberOfUpdates;

...

  @Override
  public void updateNotification(NavProgress routeProgress) {
    // Update the builder with a new number of updates
    customNotificationBuilder.setContentText("Number of updates: " + numberOfUpdates++);

    notificationManager.notify(CUSTOM_NOTIFICATION_ID, customNotificationBuilder.build());
  }

...
}
```

The _updateNotification_ method will be called every time the _NavProgress_ is updated, in this method, we can perform UI updates.

### Step 4 (Optional)

A button to stop the navigation The key to declaring a stop button is to create a pending intent to broadcast the event

1.  create a pending intent and set it as the content intent of the notification builder.
    
2.  a method to register broadcast receiver
    
3.  a method to unregister broadcast receiver
    
4.  call register method in initialize phase, for example. the constructor of the CustomNavigationNotification
    
5.  call unregister method when the navigation is stopped.
    

```java
public class CustomNavigationNotification implements NavigationNotification {

...
  private static final String STOP_NAVIGATION_ACTION = "stop_navigation_action";

  private BroadcastReceiver stopNavigationReceiver;

...

  public CustomNavigationNotification(Context applicationContext) {
...
    customNotificationBuilder = new NotificationCompat.Builder(applicationContext, NAVIGATION_NOTIFICATION_CHANNEL)
      ...
      .setContentIntent(createPendingStopIntent(applicationContext));

    register(stopNavigationReceiver, applicationContext);
...
  }

...

  @Override
  public void onNavigationStopped(Context context) {
    context.unregisterReceiver(stopNavigationReceiver);
   ...
  }

  public void register(BroadcastReceiver stopNavigationReceiver, Context applicationContext) {
    this.stopNavigationReceiver = stopNavigationReceiver;
    applicationContext.registerReceiver(stopNavigationReceiver, new IntentFilter(STOP_NAVIGATION_ACTION));
  }

private PendingIntent createPendingStopIntent(Context context) {
    Intent stopNavigationIntent = new Intent(STOP_NAVIGATION_ACTION);
    return PendingIntent.getBroadcast(context, 0, stopNavigationIntent, 0);
  }
}
```

### Full example

```java
public class CustomNavigationNotification implements NavigationNotification {

  private static final int CUSTOM_NOTIFICATION_ID = 11112;
  private static final String STOP_NAVIGATION_ACTION = "stop_navigation_action";

  private final Notification customNotification;
  private final NotificationCompat.Builder customNotificationBuilder;
  private final NotificationManager notificationManager;
  private BroadcastReceiver stopNavigationReceiver;
  private int numberOfUpdates;

  public CustomNavigationNotification(Context applicationContext) {
    notificationManager = (NotificationManager) applicationContext.getSystemService(Context.NOTIFICATION_SERVICE);

    customNotificationBuilder = new NotificationCompat.Builder(applicationContext, NAVIGATION_NOTIFICATION_CHANNEL)
      .setSmallIcon(R.drawable.ic_navigation)
      .setContentTitle("Custom Navigation Notification")
      .setContentText("Display your own content here!")
      .setContentIntent(createPendingStopIntent(applicationContext));

    customNotification = customNotificationBuilder.build();
    register(stopNavigationReceiver, applicationContext);
  }

  @Override
  public Notification getNotification() {
    return customNotification;
  }

  @Override
  public int getNotificationId() {
    return CUSTOM_NOTIFICATION_ID;
  }

  @Override
  public void updateNotification(NavProgress routeProgress) {
    // Update the builder with a new number of updates
    customNotificationBuilder.setContentText("Number of updates: " + numberOfUpdates++);

    notificationManager.notify(CUSTOM_NOTIFICATION_ID, customNotificationBuilder.build());
  }

  @Override
  public void onNavigationStopped(Context context) {
    context.unregisterReceiver(stopNavigationReceiver);
    notificationManager.cancel(CUSTOM_NOTIFICATION_ID);
  }

  public void register(BroadcastReceiver stopNavigationReceiver, Context applicationContext) {
    this.stopNavigationReceiver = stopNavigationReceiver;
    applicationContext.registerReceiver(stopNavigationReceiver, new IntentFilter(STOP_NAVIGATION_ACTION));
  }

private PendingIntent createPendingStopIntent(Context context) {
    Intent stopNavigationIntent = new Intent(STOP_NAVIGATION_ACTION);
    return PendingIntent.getBroadcast(context, 0, stopNavigationIntent, 0);
  }
}
```

The CustomNavigationNotification class is an implementation of the NavigationNotification interface, it allows developers to customize the style of the ongoing notification displayed during navigation. It creates a NotificationCompat.Builder with a custom small icon, title, and text, and assigns it to a Notification object. The updateNotification method updates the notification's text to display the number of updates. The getNotificationId method returns a unique integer identifier for the notification, and onNavigationStopped method cancels the notification and unregisters the BroadcastReceiver when navigation is stopped. The register method is used to register the stopNavigationReceiver to listen for the STOP_NAVIGATION_ACTION intent. A PendingIntent is created with the STOP_NAVIGATION_ACTION intent, which is set as the content intent of the notification.

## Custom Speechplayer

### Overview

NavViewConfig provides a field that allows developers to customize a speech player. In this article, we are going to cover how to implement a customized speed player with the following steps:

1.  init a player
    
2.  implement announcement playing method
    
3.  request audio focus when playing voice instructions
    

### Interface

SpeechPlayer is an interface that provides a way for the SDK to play voice guidance to the user during navigation. The interface has several methods that can be implemented to control the behavior of the speech player.

-   The _play()_ method is used to play a given speech announcement, multiple speech announcements will be queued in a first-in-first-out (FIFO) fashion.
    
-   The _isMuted()_ method is used to determine whether the speech player is currently muted or not.
    
-   The _setMuted()_ method is used to cancel the currently playing announcement immediately and clear any queued announcements if any.
    
-   The _onOffRoute()_ method is used to stop and release the media if needed, or play voice guidance to notify users about the off-route event.
    
-   The _onDestroy()_ method is used to stop and release the media if needed.
    
-   The _isSpeaking()_ method is used to determine whether the speech player is currently playing an announcement or not.
    
-   The _stop()_ method is used to stop the speech player if it is playing.
    

```java
public interface SpeechPlayer {

    /**
     * Will play the given string speechAnnouncement.  multiple speechAnnouncement will be queued in FIFO fashion.
     */
    void play(SpeechAnnouncement speechAnnouncement);

    /**
     * determine whether the speechPlayer is muted or not
     */
    boolean isMuted();

    /**
     * cancel currently playing announcement immediately, 
     * and clear queued announcements if any.
     */
    void setMuted(boolean isMuted);

    /**
     * Stop the current announcement if playing. 
     * or play voice guidance to notify users about the offroute event.
     */
    void onOffRoute();

    /**
     * Used to stop and release the media (if needed).
     */
    void onDestroy();

    /**
     * determine whether the speechPlayer is currently playing an announcement or not
     */
    boolean isSpeaking();

    /**
     * stop the SpeechPlayer if playing.
     */
    void stop();
}
```

A custom _SpeechPlayer_ lets you fully control voice guidance behavior and playback.

### Init a player

In Android we can use different players to play guidance, for example - _TextToSpeech_ or _MediaPlayer_.

In this example, we are going to use TextToSpeech.

```java
import android.speech.tts.TextToSpeech;

class AndroidSpeechPlayer implements SpeechPlayer {
private final TextToSpeech textToSpeech;
private boolean speechHasInit = false;
private boolean languageSupported = false;
private boolean isMuted;

AndroidSpeechPlayer(Context context, final String language) {
        textToSpeech = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
            @Override
            public void onInit(int status) {
                boolean ableToInitialize = status == TextToSpeech.SUCCESS && language != null;
                if (!ableToInitialize) {
                    return;
                }  
                speechHasInit = true;
                setLanguage(new Locale(language));
            }
        });
}

private void setLanguage(Locale language) {
        boolean isLanguageAvailable = textToSpeech.isLanguageAvailable(language) == TextToSpeech.LANG_AVAILABLE;
        if (!isLanguageAvailable) {
            Log.w("The specified language is not supported by TTS");
            return;
        }
        languageSupported = true;
        textToSpeech.setLanguage(language);
    }
  ...
}
```

The **AndroidSpeechPlayer** class is an implementation of the _SpeechPlayer_ interface. It uses the _TextToSpeech_ class to play speech announcements. The class takes in a _Context_ and a _language_ string in its constructor and initializes the _TextToSpeech_ object with the specified language. The class also has methods to check whether the speech player is muted, set the language, play speech announcements, stop the speech player, check if the player is speaking and other methods that follow the _SpeechPlayer_ interface.

### Implement play announcement

```java
   @Override
    public void play(SpeechAnnouncement speechAnnouncement) {
        boolean isValidAnnouncement = speechAnnouncement != null
                && !TextUtils.isEmpty(speechAnnouncement.announcement());
        boolean canPlay = isValidAnnouncement && languageSupported && !isMuted;
        if (!canPlay) {
            return;
        }

        HashMap<String, String> params = new HashMap<>(1);
        params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, DEFAULT_UTTERANCE_ID);
        textToSpeech.speak(speechAnnouncement.announcement(), TextToSpeech.QUEUE_ADD, params);
    }

    @Override
    public boolean isMuted() {
        return isMuted;
    }

    @Override
    public void setMuted(boolean isMuted) {
        this.isMuted = isMuted;
        if (isMuted) {
            muteTts();
        }
    }
  
    @Override
    public void onOffRoute() {
       stop();
    }

    @Override
    public void onDestroy() {
        if (textToSpeech != null) {
            textToSpeech.stop();
            textToSpeech.shutdown();
        }
    }

    @Override
    public boolean isSpeaking() {
     if (textToSpeech != null) {
        return textToSpeech.isSpeaking(); 
     }
     return false;
    }

    @Override
    public void stop() {
        if (textToSpeech != null && textToSpeech.isSpeaking()) {
            textToSpeech.stop();
        }
   }
```

The above code demonstrates an example implementation of the SpeechPlayer interface, including methods for playing announcements, muting the speech player, handling off-route events, cleaning up resources, and checking the speaking status of the speech player.

### Handle Audio Focus

Two or more Android apps can play audio to the same output stream simultaneously, and the system mixes everything together. While this is technically impressive, it can be very aggravating to a user. To avoid every music app playing at the same time, Android introduces the idea of _audio focus_. Only one app can hold audio focus at a time.

More details at [https://developer.android.com/guide/topics/media-apps/audio-focus](https://developer.android.com/guide/topics/media-apps/audio-focus)

Voice guidance is important to drivers, it's necessary to obtain the audio focus when we play voice guidance. Add the code below to our customised speech player:

```java
…

private AudioManager audioManager;
private AudioFocusRequest audioFocusRequest;

...

private void initAudioManager(Context context) {
    audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        audioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK).build();
    }
}

private void requestFocus() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        audioManager.requestAudioFocus(audioFocusRequest);
    } else {
        audioManager.requestAudioFocus(null, AudioManager.STREAM_MUSIC,
                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
    }
}

private void abandonFocus() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        audioManager.abandonAudioFocusRequest(audioFocusRequest);
    } else {
        audioManager.abandonAudioFocus(null);
   }
}
```

And then we can modify the constructor:

```java
AndroidSpeechPlayer(Context context, final String language) {
    initAudioManager(context);
    textToSpeech = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
        @Override
        public void onInit(int status) {
            boolean ableToInitialize = status == TextToSpeech.SUCCESS && language != null;
            if (!ableToInitialize) {
                Timber.e("There was an error initializing native TTS");
                return;
            }
            setLanguage(new Locale(language));
            speechHasInit = true;
            textToSpeech.setOnUtteranceProgressListener(new UtteranceProgressListener() {
                @Override
                public void onStart(String s) {
                    requestFocus();
                }

                @Override
                public void onDone(String s) {
                    abandonFocus();
                }

                @Override
                public void onError(String s) {

                }
            });
        }
    });
}
```

The above code creates an instance of the Android built-in Text-To-Speech (TTS) engine and uses the AudioManager to request and abandon audio focus from the Android system.

To handle audio focus properly, we need to create an UtteranceProgressListener to hook audio focus handling into the TTS,
