Skip to content

Notification trigger API #4183

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Oct 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions firebase-appdistribution-api/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ package com.google.firebase.appdistribution {
}

public interface FirebaseAppDistribution {
method public void cancelFeedbackNotification();
method @NonNull public com.google.android.gms.tasks.Task<com.google.firebase.appdistribution.AppDistributionRelease> checkForNewRelease();
method @NonNull public static com.google.firebase.appdistribution.FirebaseAppDistribution getInstance();
method public boolean isTesterSignedIn();
method public void showFeedbackNotification(@StringRes int, @NonNull com.google.firebase.appdistribution.InterruptionLevel);
method public void showFeedbackNotification(@NonNull CharSequence, @NonNull com.google.firebase.appdistribution.InterruptionLevel);
method @NonNull public com.google.android.gms.tasks.Task<java.lang.Void> signInTester();
method public void signOutTester();
method public void startFeedback(int);
method public void startFeedback(@StringRes int);
method public void startFeedback(@NonNull CharSequence);
method public void startFeedback(@NonNull int, @Nullable android.net.Uri);
method public void startFeedback(@StringRes int, @Nullable android.net.Uri);
method public void startFeedback(@NonNull CharSequence, @Nullable android.net.Uri);
method @NonNull public com.google.firebase.appdistribution.UpdateTask updateApp();
method @NonNull public com.google.firebase.appdistribution.UpdateTask updateIfNewReleaseAvailable();
Expand All @@ -46,6 +49,14 @@ package com.google.firebase.appdistribution {
enum_constant public static final com.google.firebase.appdistribution.FirebaseAppDistributionException.Status UPDATE_NOT_AVAILABLE;
}

public enum InterruptionLevel {
enum_constant public static final com.google.firebase.appdistribution.InterruptionLevel DEFAULT;
enum_constant public static final com.google.firebase.appdistribution.InterruptionLevel HIGH;
enum_constant public static final com.google.firebase.appdistribution.InterruptionLevel LOW;
enum_constant public static final com.google.firebase.appdistribution.InterruptionLevel MAX;
enum_constant public static final com.google.firebase.appdistribution.InterruptionLevel MIN;
}

public interface OnProgressListener {
method public void onProgressUpdate(@NonNull com.google.firebase.appdistribution.UpdateProgress);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@

package com.google.firebase.appdistribution;

import android.app.Activity;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import com.google.android.gms.tasks.Task;
import com.google.firebase.FirebaseApp;
import com.google.firebase.appdistribution.internal.FirebaseAppDistributionProxy;
Expand All @@ -34,6 +36,7 @@
* <p>Call {@link #getInstance()} to get the singleton instance of {@link FirebaseAppDistribution}.
*/
public interface FirebaseAppDistribution {

/**
* Updates the app to the newest release, if one is available.
*
Expand Down Expand Up @@ -125,7 +128,7 @@ public interface FirebaseAppDistribution {
* @param infoTextResourceId string resource ID of text to display to the tester before collecting
* feedback data (e.g. Terms and Conditions)
*/
void startFeedback(int infoTextResourceId);
void startFeedback(@StringRes int infoTextResourceId);

/**
* Takes a screenshot, and starts an activity to collect and submit feedback from the tester.
Expand Down Expand Up @@ -159,7 +162,7 @@ public interface FirebaseAppDistribution {
* @param screenshot URI to a bitmap containing a screenshot that will be included with the
* report, or null to not include a screenshot
*/
void startFeedback(@NonNull int infoTextResourceId, @Nullable Uri screenshot);
void startFeedback(@StringRes int infoTextResourceId, @Nullable Uri screenshot);

/**
* Starts an activity to collect and submit feedback from the tester, along with the given
Expand All @@ -179,6 +182,70 @@ public interface FirebaseAppDistribution {
*/
void startFeedback(@NonNull CharSequence infoText, @Nullable Uri screenshot);

/**
* Displays a notification that, when tapped, will take a screenshot of the current activity, then
* start a new activity to collect and submit feedback from the tester along with the screenshot.
*
* <p>On Android 13 and above, this method requires the <a
* href="https://developer.android.com/develop/ui/views/notifications/notification-permission">runtime
* permission for sending notifications</a>: {@code POST_NOTIFICATIONS}. If your app targets
* Android 13 (API level 33) or above, you should <a
* href="https://developer.android.com/training/permissions/requesting">request the
* permission</a>.
*
* <p>When the notification is tapped:
*
* <ol>
* <li>If the app is open, take a screenshot of the current activity
* <li>If tester is not signed in, presents the tester with a Google Sign-in UI
* <li>Starts a full screen activity for the tester to compose and submit the feedback
* </ol>
*
* @param infoTextResourceId string resource ID of text to display to the tester before collecting
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the underlying api require a resource id? if not, do we want to restrict to only allow resource ids as opposed to allowing passing in custom strings like constants?

If resourceId is preferred, please make sure to annotate this param with @StringRes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We alternatively allow them to pass in a CharSequence, below. Supporting resource id is mostly for convenience, so they don't have to call Context.getText() or Context.getString(). This matches the options we already provide with startFeedback(int) vs startFeedback(CharSequence). If this is creating too much clutter in the API, maybe we just support CharSequence? It's not that big a deal to call getText.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My preference is in the other direction. If we eliminate one, I'd vote for the overload with CharSequence and only keep @StringRes int. The latter is a best practice. Hard-coded strings are smelly. Or we keep both, but add JavaDoc which explains that @StringRes int is preferred.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point, Kai. I actually just noticed that if you use TextView.setText(CharSequence) you get a compiler warning: "String literal in setText can not be translated. Use Android resources instead."

So I'm tempted to remove the CharSequence option, but that would prevent the dev from using format strings: https://developer.android.com/guide/topics/resources/string-resource#formatting-strings

I feel like we should support that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to add an overload that would mirror getResource(int, Object...)? i.e. something like

void showFeedbackNotification(
      @NonNull InterruptionLevel interruptionLevel, @StringRes int infoTextResourceId, Object... formatArgs);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could, but if they want to use a format string AND html elements like <a> tags they would have to do some extra steps and then final pass us a styled CharSequence: https://developer.android.com/guide/topics/resources/string-resource#StylingWithHTML

This might seem like an edge case, but because this text will likely include links to terms and conditions I expect anyone who wants to use a format string will have to do this.

* feedback data (e.g. Terms and Conditions)
* @param interruptionLevel the level of interruption for the feedback notification. On platforms
* below Android 8, this corresponds to a notification channel importance and once set cannot
* be changed except by the user.
*/
void showFeedbackNotification(
@StringRes int infoTextResourceId, @NonNull InterruptionLevel interruptionLevel);

/**
* Displays a notification that, when tapped, will take a screenshot of the current activity, then
* start a new activity to collect and submit feedback from the tester along with the screenshot.
*
* <p>On Android 13 and above, this method requires the <a
* href="https://developer.android.com/develop/ui/views/notifications/notification-permission">runtime
* permission for sending notifications</a>: {@code POST_NOTIFICATIONS}. If your app targets
* Android 13 (API level 33) or above, you should <a
* href="https://developer.android.com/training/permissions/requesting">request the
* permission</a>.
*
* <p>When the notification is tapped:
*
* <ol>
* <li>If the app is open, take a screenshot of the current activity
* <li>If tester is not signed in, presents the tester with a Google Sign-in UI
* <li>Starts a full screen activity for the tester to compose and submit the feedback
* </ol>
*
* @param infoText text to display to the tester before collecting feedback data (e.g. Terms and
* Conditions)
* @param interruptionLevel the level of interruption for the feedback notification. On platforms
* below Android 8, this corresponds to a notification channel importance and once set cannot
* be changed except by the user.
*/
void showFeedbackNotification(
@NonNull CharSequence infoText, @NonNull InterruptionLevel interruptionLevel);

/**
* Hides the notification shown with {@link #showFeedbackNotification}.
*
* <p>This should be called in the {@link Activity#onDestroy} of the activity that showed the
* notification.
*/
void cancelFeedbackNotification();

/** Gets the singleton {@link FirebaseAppDistribution} instance. */
@NonNull
static FirebaseAppDistribution getInstance() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.firebase.appdistribution;

import android.app.NotificationManager;
import androidx.core.app.NotificationCompat;

/** An enum specifying the level of interruption of a notification when it is created. */
public enum InterruptionLevel {

/**
* Minimum interruption level.
*
* <p>Translates to {@link NotificationManager#IMPORTANCE_MIN} on Android O+ and {@link
* NotificationCompat#PRIORITY_MIN} on older platforms.
*/
MIN(NotificationManager.IMPORTANCE_MIN, NotificationCompat.PRIORITY_MIN),

/**
* Low interruption level.
*
* <p>Translates to {@link NotificationManager#IMPORTANCE_LOW} on Android O+ and {@link
* NotificationCompat#PRIORITY_LOW} on older platforms.
*/
LOW(NotificationManager.IMPORTANCE_LOW, NotificationCompat.PRIORITY_LOW),

/**
* Default interruption level.
*
* <p>Translates to {@link NotificationManager#IMPORTANCE_DEFAULT} on Android O+ and {@link
* NotificationCompat#PRIORITY_DEFAULT} on older platforms.
*/
DEFAULT(NotificationManager.IMPORTANCE_DEFAULT, NotificationCompat.PRIORITY_DEFAULT),

/**
* High interruption level.
*
* <p>Translates to {@link NotificationManager#IMPORTANCE_HIGH} on Android O+ and {@link
* NotificationCompat#PRIORITY_HIGH} on older platforms.
*/
HIGH(NotificationManager.IMPORTANCE_HIGH, NotificationCompat.PRIORITY_HIGH),

/**
* Maximum interruption level.
*
* <p>Translates to {@link NotificationManager#IMPORTANCE_HIGH} on Android O+ and {@link
* NotificationCompat#PRIORITY_MAX} on older platforms.
*/
MAX(NotificationManager.IMPORTANCE_HIGH, NotificationCompat.PRIORITY_MAX);

/**
* The notification channel importance corresponding to this interruption level on Android O+.
*
* @hide
*/
public final int channelImportance;

/**
* The notification priority corresponding to this interruption level on older platforms.
*
* @hide
*/
public final int notificationPriority;

InterruptionLevel(int channelImportance, int notificationPriority) {
this.channelImportance = channelImportance;
this.notificationPriority = notificationPriority;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import com.google.android.gms.tasks.Task;
import com.google.firebase.appdistribution.AppDistributionRelease;
import com.google.firebase.appdistribution.FirebaseAppDistribution;
import com.google.firebase.appdistribution.FirebaseAppDistributionException;
import com.google.firebase.appdistribution.InterruptionLevel;
import com.google.firebase.appdistribution.UpdateTask;
import com.google.firebase.inject.Provider;

Expand Down Expand Up @@ -75,7 +77,7 @@ public UpdateTask updateApp() {
}

@Override
public void startFeedback(int infoTextResourceId) {
public void startFeedback(@StringRes int infoTextResourceId) {
delegate.startFeedback(infoTextResourceId);
}

Expand All @@ -85,12 +87,29 @@ public void startFeedback(@NonNull CharSequence infoText) {
}

@Override
public void startFeedback(@NonNull int infoTextResourceId, @Nullable Uri screenshotUri) {
public void startFeedback(@StringRes int infoTextResourceId, @Nullable Uri screenshotUri) {
delegate.startFeedback(infoTextResourceId, screenshotUri);
}

@Override
public void startFeedback(@NonNull CharSequence infoText, @Nullable Uri screenshotUri) {
delegate.startFeedback(infoText, screenshotUri);
}

@Override
public void showFeedbackNotification(
@StringRes int infoTextResourceId, @NonNull InterruptionLevel interruptionLevel) {
delegate.showFeedbackNotification(infoTextResourceId, interruptionLevel);
}

@Override
public void showFeedbackNotification(
@NonNull CharSequence infoText, @NonNull InterruptionLevel interruptionLevel) {
delegate.showFeedbackNotification(infoText, interruptionLevel);
}

@Override
public void cancelFeedbackNotification() {
delegate.cancelFeedbackNotification();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import com.google.android.gms.tasks.Continuation;
import com.google.android.gms.tasks.OnCanceledListener;
import com.google.android.gms.tasks.OnCompleteListener;
Expand All @@ -30,6 +31,7 @@
import com.google.firebase.appdistribution.FirebaseAppDistribution;
import com.google.firebase.appdistribution.FirebaseAppDistributionException;
import com.google.firebase.appdistribution.FirebaseAppDistributionException.Status;
import com.google.firebase.appdistribution.InterruptionLevel;
import com.google.firebase.appdistribution.OnProgressListener;
import com.google.firebase.appdistribution.UpdateTask;
import java.util.concurrent.Executor;
Expand Down Expand Up @@ -73,17 +75,28 @@ public UpdateTask updateApp() {
}

@Override
public void startFeedback(int infoTextResourceId) {}
public void startFeedback(@StringRes int infoTextResourceId) {}

@Override
public void startFeedback(@NonNull CharSequence infoText) {}

@Override
public void startFeedback(@NonNull int infoTextResourceId, @Nullable Uri screenshotUri) {}
public void startFeedback(@StringRes int infoTextResourceId, @Nullable Uri screenshotUri) {}

@Override
public void startFeedback(@NonNull CharSequence infoText, @Nullable Uri screenshotUri) {}

@Override
public void showFeedbackNotification(
@StringRes int infoTextResourceId, @NonNull InterruptionLevel interruptionLevel) {}

@Override
public void showFeedbackNotification(
@NonNull CharSequence infoText, @NonNull InterruptionLevel interruptionLevel) {}

@Override
public void cancelFeedbackNotification() {}

private static <TResult> Task<TResult> getNotImplementedTask() {
return Tasks.forException(
new FirebaseAppDistributionException(
Expand Down
7 changes: 6 additions & 1 deletion firebase-appdistribution/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
package="com.google.firebase.appdistribution.impl">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<application>
Expand Down Expand Up @@ -51,6 +52,10 @@
android:exported="false"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />

<activity
android:name=".TakeScreenshotAndStartFeedbackActivity"
android:exported="false" />

<provider
android:name="com.google.firebase.appdistribution.impl.FirebaseAppDistributionFileProvider"
android:authorities="${applicationId}.FirebaseAppDistributionFileProvider"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ private void postUpdateProgress(
.build());
}
if (showNotification) {
appDistributionNotificationsManager.updateNotification(
appDistributionNotificationsManager.showAppUpdateNotification(
totalBytes, downloadedBytes, stringResourceId);
}
}
Expand Down
Loading