-
Notifications
You must be signed in to change notification settings - Fork 288
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Handle new notification permission. (#370)
Handle new notification permission.
- Loading branch information
Showing
8 changed files
with
346 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
...va/com/google/androidbrowserhelper/trusted/NotificationDelegationExtraCommandHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
// Copyright 2022 Google Inc. All Rights Reserved. | ||
// | ||
// 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.androidbrowserhelper.trusted; | ||
|
||
import android.app.PendingIntent; | ||
import android.content.Context; | ||
import android.os.Bundle; | ||
import android.text.TextUtils; | ||
import androidx.annotation.NonNull; | ||
import androidx.annotation.Nullable; | ||
import androidx.browser.trusted.TrustedWebActivityCallbackRemote; | ||
|
||
/** | ||
* Handles extra commands related to notification delegation such as checking and requesting permission. | ||
*/ | ||
public class NotificationDelegationExtraCommandHandler implements ExtraCommandHandler { | ||
static final String COMMAND_CHECK_NOTIFICATION_PERMISSION = | ||
"checkNotificationPermission"; | ||
private static final String COMMAND_GET_NOTIFICATION_PERMISSION_REQUEST_PENDING_INTENT = | ||
"getNotificationPermissionRequestPendingIntent"; | ||
private static final String KEY_NOTIFICATION_CHANNEL_NAME = "notificationChannelName"; | ||
private static final String KEY_NOTIFICATION_PERMISSION_REQUEST_PENDING_INTENT = | ||
"notificationPermissionRequestPendingIntent"; | ||
|
||
@NonNull | ||
@Override | ||
public Bundle handleExtraCommand(Context context, @NonNull String commandName, @NonNull Bundle args, | ||
@Nullable TrustedWebActivityCallbackRemote callback) { | ||
Bundle commandResult = new Bundle(); | ||
commandResult.putBoolean(EXTRA_COMMAND_SUCCESS, false); | ||
String channelName = args.getString(KEY_NOTIFICATION_CHANNEL_NAME); | ||
switch (commandName) { | ||
case COMMAND_CHECK_NOTIFICATION_PERMISSION: | ||
if (TextUtils.isEmpty(channelName)) break; | ||
|
||
boolean enabled = NotificationUtils.areNotificationsEnabled(context, channelName); | ||
@PermissionStatus int status = enabled ? PermissionStatus.ALLOW : PermissionStatus.BLOCK; | ||
if (status == PermissionStatus.BLOCK && !PrefUtils.hasRequestedNotificationPermission(context)) { | ||
status = PermissionStatus.ASK; | ||
} | ||
commandResult.putInt(NotificationPermissionRequestActivity.KEY_PERMISSION_STATUS, status); | ||
commandResult.putBoolean(EXTRA_COMMAND_SUCCESS, true); | ||
break; | ||
case COMMAND_GET_NOTIFICATION_PERMISSION_REQUEST_PENDING_INTENT: | ||
if (TextUtils.isEmpty(channelName)) break; | ||
|
||
PendingIntent pendingIntent = NotificationPermissionRequestActivity.createPermissionRequestPendingIntent( | ||
context, channelName); | ||
commandResult.putParcelable(KEY_NOTIFICATION_PERMISSION_REQUEST_PENDING_INTENT, pendingIntent); | ||
commandResult.putBoolean(EXTRA_COMMAND_SUCCESS, true); | ||
break; | ||
} | ||
return commandResult; | ||
} | ||
} |
123 changes: 123 additions & 0 deletions
123
...n/java/com/google/androidbrowserhelper/trusted/NotificationPermissionRequestActivity.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
// Copyright 2022 Google Inc. All Rights Reserved. | ||
// | ||
// 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.androidbrowserhelper.trusted; | ||
|
||
import android.app.Activity; | ||
import android.app.PendingIntent; | ||
import android.content.Context; | ||
import android.content.Intent; | ||
import android.content.pm.PackageManager; | ||
import android.os.Build; | ||
import android.os.Bundle; | ||
import android.os.Message; | ||
import android.os.Messenger; | ||
import android.os.RemoteException; | ||
|
||
import android.util.Log; | ||
import androidx.core.app.ActivityCompat; | ||
|
||
/** | ||
* A simple transparent activity for requesting the notification permission. On either approve or disapprove, this will | ||
* send the result via the {@link Messenger} provided with the intent, and then finish. | ||
*/ | ||
public class NotificationPermissionRequestActivity extends Activity { | ||
private static final String TAG = "Notifications"; | ||
|
||
static final String KEY_PERMISSION_STATUS = "permissionStatus"; | ||
|
||
// TODO: Use Manifest.permission.POST_NOTIFICATIONS when it is released. | ||
private static final String PERMISSION_POST_NOTIFICATIONS = "android.permission.POST_NOTIFICATIONS"; | ||
|
||
// TODO: Use Build.VERSION_CODES when it is released. | ||
private static final int VERSION_T = 33; | ||
|
||
private static final String EXTRA_NOTIFICATION_CHANNEL_NAME = "notificationChannelName"; | ||
private static final String EXTRA_MESSENGER = "messenger"; | ||
|
||
private String mChannelName; | ||
private Messenger mMessenger; | ||
|
||
/** | ||
* Creates a {@link PendingIntent} for launching this activity to request the notification permission. It is mutable | ||
* so that a messenger extra can be added for returning the permission request result. | ||
*/ | ||
public static PendingIntent createPermissionRequestPendingIntent(Context context, String channelName) { | ||
Intent intent = new Intent(context.getApplicationContext(), NotificationPermissionRequestActivity.class); | ||
intent.putExtra(EXTRA_NOTIFICATION_CHANNEL_NAME, channelName); | ||
// Starting with Build.VERSION_CODES.S it is required to explicitly specify the mutability of PendingIntents. | ||
int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? PendingIntent.FLAG_MUTABLE : 0; | ||
return PendingIntent.getActivity(context.getApplicationContext(),0, intent, flags); | ||
} | ||
|
||
@Override | ||
protected void onCreate(Bundle savedInstanceState) { | ||
super.onCreate(savedInstanceState); | ||
|
||
mChannelName = getIntent().getStringExtra(EXTRA_NOTIFICATION_CHANNEL_NAME); | ||
mMessenger = getIntent().getParcelableExtra(EXTRA_MESSENGER); | ||
if (mChannelName == null || mMessenger == null) { | ||
Log.w(TAG, "Finishing because no channel name or messenger for returning the result was provided."); | ||
finish(); | ||
return; | ||
} | ||
|
||
// When running on T or greater, with the app targeting less than T, creating a channel for the first time will | ||
// trigger the permission dialog. | ||
if (Build.VERSION.SDK_INT >= VERSION_T && getApplicationContext().getApplicationInfo().targetSdkVersion < VERSION_T) { | ||
NotificationUtils.createNotificationChannel(this, mChannelName); | ||
} | ||
|
||
ActivityCompat.requestPermissions(this, new String[]{PERMISSION_POST_NOTIFICATIONS}, 0); | ||
} | ||
|
||
@Override | ||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { | ||
boolean enabled = false; | ||
for (int i = 0; i < permissions.length; i++) { | ||
if (!permissions[i].equals(PERMISSION_POST_NOTIFICATIONS)) continue; | ||
|
||
PrefUtils.setHasRequestedNotificationPermission(this); | ||
enabled = grantResults[i] == PackageManager.PERMISSION_GRANTED; | ||
break; | ||
} | ||
|
||
// This method will only receive the notification permission and its grant result when running on and | ||
// targeting >= T. Check whether notifications are actually enabled, perhaps because the system displayed a | ||
// permission dialog after the first notification channel was created and the user approved it. | ||
if (!enabled) { | ||
enabled = NotificationUtils.areNotificationsEnabled(this, mChannelName); | ||
} | ||
|
||
sendPermissionMessage(mMessenger, enabled); | ||
finish(); | ||
} | ||
|
||
/** | ||
* Sends a message to the messenger containing the permission status. | ||
*/ | ||
private static void sendPermissionMessage(Messenger messenger, boolean enabled) { | ||
Bundle data = new Bundle(); | ||
@PermissionStatus int status = enabled ? PermissionStatus.ALLOW : PermissionStatus.BLOCK; | ||
data.putInt(KEY_PERMISSION_STATUS, status); | ||
Message message = Message.obtain(); | ||
message.setData(data); | ||
|
||
try { | ||
messenger.send(message); | ||
} catch (RemoteException e) { | ||
e.printStackTrace(); | ||
} | ||
} | ||
} |
61 changes: 61 additions & 0 deletions
61
...rowserhelper/src/main/java/com/google/androidbrowserhelper/trusted/NotificationUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
// Copyright 2022 Google Inc. All Rights Reserved. | ||
// | ||
// 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.androidbrowserhelper.trusted; | ||
|
||
import android.app.NotificationChannel; | ||
import android.app.NotificationManager; | ||
import android.content.Context; | ||
import android.os.Build; | ||
import androidx.core.app.NotificationManagerCompat; | ||
import java.util.Locale; | ||
|
||
/** | ||
* Helper for interacting with the notification manager and channels. | ||
*/ | ||
public class NotificationUtils { | ||
private NotificationUtils() {} | ||
|
||
/** | ||
* Returns true if notifications are enabled and either the channel does not exist or it has not been disabled. | ||
*/ | ||
public static boolean areNotificationsEnabled(Context context, String channelName) { | ||
if (!NotificationManagerCompat.from(context).areNotificationsEnabled()) return false; | ||
|
||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return true; | ||
|
||
NotificationChannel channel = | ||
NotificationManagerCompat.from(context).getNotificationChannel(channelNameToId(channelName)); | ||
return channel == null || channel.getImportance() != NotificationManager.IMPORTANCE_NONE; | ||
} | ||
|
||
/** | ||
* Creates a notification channel using the given channel name. | ||
*/ | ||
public static void createNotificationChannel(Context context, String channelName) { | ||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return; | ||
|
||
NotificationChannel channel = new NotificationChannel(channelNameToId(channelName), | ||
channelName, NotificationManager.IMPORTANCE_DEFAULT); | ||
NotificationManagerCompat.from(context).createNotificationChannel(channel); | ||
} | ||
|
||
/** | ||
* Generates a notification channel id from a channel name. | ||
* TODO: Remove this when we can use the method defined in AndroidX instead. | ||
*/ | ||
private static String channelNameToId(String name) { | ||
return name.toLowerCase(Locale.ROOT).replace(' ', '_') + "_channel_id"; | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
...browserhelper/src/main/java/com/google/androidbrowserhelper/trusted/PermissionStatus.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// Copyright 2022 Google Inc. All Rights Reserved. | ||
// | ||
// 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.androidbrowserhelper.trusted; | ||
|
||
import androidx.annotation.IntDef; | ||
|
||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
|
||
/** | ||
* Represents the permission state in TWA service calls. | ||
*/ | ||
@IntDef({ | ||
PermissionStatus.ALLOW, PermissionStatus.BLOCK, PermissionStatus.ASK | ||
}) | ||
@Retention(RetentionPolicy.SOURCE) | ||
public @interface PermissionStatus { | ||
int ALLOW = 0; | ||
int BLOCK = 1; | ||
int ASK = 2; | ||
} |
45 changes: 45 additions & 0 deletions
45
androidbrowserhelper/src/main/java/com/google/androidbrowserhelper/trusted/PrefUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// Copyright 2022 Google Inc. All Rights Reserved. | ||
// | ||
// 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.androidbrowserhelper.trusted; | ||
|
||
import android.content.Context; | ||
import android.content.SharedPreferences; | ||
|
||
/** | ||
* Helper for using application level {@link SharedPreferences} in a consistent way, with the same | ||
* file name and using the application context. | ||
*/ | ||
public class PrefUtils { | ||
private PrefUtils() {} | ||
|
||
private static final String SHARED_PREFERENCES_NAME = "com.google.androidbrowserhelper"; | ||
private static final String KEY_HAS_REQUESTED_NOTIFICATION_PERMISSION = "HAS_REQUESTED_NOTIFICATION_PERMISSION"; | ||
|
||
/** | ||
* Returns the application level {@link SharedPreferences} using the application context. | ||
*/ | ||
public static SharedPreferences getAppSharedPreferences(Context context) { | ||
return context.getApplicationContext().getSharedPreferences(SHARED_PREFERENCES_NAME, | ||
Context.MODE_PRIVATE); | ||
} | ||
|
||
public static boolean hasRequestedNotificationPermission(Context context) { | ||
return getAppSharedPreferences(context).getBoolean(KEY_HAS_REQUESTED_NOTIFICATION_PERMISSION, false); | ||
} | ||
|
||
public static void setHasRequestedNotificationPermission(Context context) { | ||
getAppSharedPreferences(context).edit().putBoolean(KEY_HAS_REQUESTED_NOTIFICATION_PERMISSION, true).apply(); | ||
} | ||
} |
Oops, something went wrong.