This commit is contained in:
xsl
2025-09-04 10:54:47 +08:00
commit 6bc8f61b18
1808 changed files with 208268 additions and 0 deletions
@@ -0,0 +1,152 @@
/* Copyright (c) 2019-2021, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* 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.khronos.vulkan_samples;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import com.khronos.vulkan_samples.common.Utils;
import java.util.ArrayList;
import java.util.List;
public class FilterDialog extends DialogFragment {
// The adapter that is notified by a filter changing
private ViewPagerAdapter adapter;
// Saved raw filters that are used to update the adapter
private List<String> filters;
// Capitalised raw filters
private List<String> labels;
// Save state of the current applied filter
// This is used to initialise the filter every time it is opened
protected List<Boolean> values;
// Mutable copy of the saved state
// This is used when a dialog is open to track the state of the labels
// If a filter is applied values is replaced with this list; otherwise This is discarded
protected List<Boolean> alteredValues;
public void setup(ViewPagerAdapter adapter, List<String> filters) {
this.adapter = adapter;
this.filters = new ArrayList<>(filters);
this.labels = new ArrayList<>(filters.size());
this.values = new ArrayList<>(filters.size());
// Initialise labels only once
// Default set all filters to checked
for (int i = 0; i < filters.size(); ++i) {
this.labels.add(i, Utils.capitalize(filters.get(i)));
this.values.add(i, true);
}
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Initial labels and values for the list of filters
final CharSequence[] labels = this.labels.toArray(new CharSequence[0]);
boolean[] values = Utils.toBoolArray(this.values);
// Initiate the mutable filter list
this.alteredValues = new ArrayList<>(this.values);
AlertDialog.Builder builder = new AlertDialog.Builder(getContext())
.setTitle("Filter Samples by Tag")
.setMultiChoiceItems(labels, values, new FilterMultiChoiceClickListener(this))
.setPositiveButton("Apply", new FilterClickListener(this));
return builder.create();
}
/**
* Notify the adapter that the current applied filter has changed
*/
protected void notifyAdapter() {
adapter.applyFilter(getFilter());
adapter.notifyDataSetChanged();
}
/**
* Get all raw filters that have been applied
*
* @return List of applied filters
*/
public List<String> getFilter() {
List<String> filters = new ArrayList<>();
for (int i = 0; i < this.filters.size(); i++) {
if (this.values.get(i)) {
filters.add(this.filters.get(i));
}
}
return filters;
}
}
/**
* Changes to a filter item's state must not persist if the dialog is closed without applying a filter.
* Update alteredValues, changes are discarded if the filter is not applied
*/
class FilterMultiChoiceClickListener implements DialogInterface.OnMultiChoiceClickListener {
private final FilterDialog dialog;
FilterMultiChoiceClickListener(FilterDialog dialog) {
this.dialog = dialog;
}
@Override
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
if (which > this.dialog.alteredValues.size() || which < 0) {
// Which is not in the label array - should never happen
return;
}
this.dialog.alteredValues.set(which, isChecked);
}
}
/**
* When applying a filter override the current values and then notify the adapter that the current filter has been changed
*/
class FilterClickListener implements DialogInterface.OnClickListener {
private final FilterDialog dialog;
FilterClickListener(FilterDialog dialog) {
this.dialog = dialog;
}
@Override
public void onClick(DialogInterface dialog, int id) {
this.dialog.values = new ArrayList<>(this.dialog.alteredValues);
this.dialog.notifyAdapter();
}
}
@@ -0,0 +1,102 @@
/* Copyright (c) 2019-2024, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* 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.khronos.vulkan_samples;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.FileProvider;
import android.view.View;
import com.google.androidgamesdk.GameActivity;
import com.khronos.vulkan_samples.common.Notifications;
import java.io.File;
import java.util.concurrent.Semaphore;
public class NativeSampleActivity extends GameActivity {
private Context context;
private final Semaphore semaphore = new Semaphore(0, true);
@Override
protected void onCreate(Bundle instance) {
super.onCreate(instance);
Notifications.initNotificationChannel(this);
context = this;
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_FULLSCREEN);
}
}
/***
* Handle the application when an error is thrown and display a message
*/
public void fatalError(String message) {
final NativeSampleActivity activity = this;
ApplicationInfo applicationInfo = activity.getApplicationInfo();
this.runOnUiThread(new Runnable() {
public void run() {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle("Vulkan Samples");
builder.setMessage(message);
builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
semaphore.release();
}
});
builder.setCancelable(false);
AlertDialog dialog = builder.create();
dialog.show();
}
});
try {
semaphore.acquire();
}
catch (InterruptedException e) { }
activity.finish();
}
}
@@ -0,0 +1,70 @@
/* Copyright (c) 2019-2021, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* 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.khronos.vulkan_samples;
import android.annotation.SuppressLint;
import android.content.Context;
import androidx.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import com.khronos.vulkan_samples.model.Sample;
import java.util.List;
public class SampleArrayAdapter extends ArrayAdapter<Sample> {
SampleArrayAdapter(Context context, List<Sample> sample_list) {
super(context, R.layout.listview_item, sample_list);
}
/**
* Get a view for a position in the sample list
*
* @param position The position in the list
* @param view The view object that was previously at this position
* @param parent The parent view group
* @return A view object representing a given sample at a list position
*/
@NonNull
@SuppressLint("SetTextI18n")
@Override
public View getView(int position, View view, @NonNull ViewGroup parent) {
// Recycle view objects
if (view == null) {
view = LayoutInflater.from(getContext()).inflate(R.layout.listview_item, parent, false);
}
Sample sample = getItem(position);
assert sample != null;
TextView title = view.findViewById(R.id.title_text);
title.setText(sample.getName());
TextView description = view.findViewById(R.id.description_text);
description.setText(sample.getDescription());
TextView vendorTag = view.findViewById(R.id.vendor_text);
vendorTag.setText(sample.getTagText());
return view;
}
}
@@ -0,0 +1,336 @@
/* Copyright (c) 2019-2024, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* 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.khronos.vulkan_samples;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
import androidx.appcompat.widget.Toolbar;
import com.khronos.vulkan_samples.common.Notifications;
import com.khronos.vulkan_samples.model.Permission;
import com.khronos.vulkan_samples.model.Sample;
import com.khronos.vulkan_samples.model.SampleStore;
import com.khronos.vulkan_samples.views.PermissionView;
import com.khronos.vulkan_samples.views.SampleListView;
public class SampleLauncherActivity extends AppCompatActivity {
SampleListView sampleListView;
PermissionView permissionView;
private boolean isBenchmarkMode = false;
private boolean isHeadless = false;
public SampleStore samples;
// Required sample permissions
List<Permission> permissions;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if (loadNativeLibrary(getResources().getString(R.string.native_lib_name))) {
// Get sample info from cpp cmake generated file
samples = new SampleStore(Arrays.asList(getSamples()));
}
// Required Permissions
permissions = new ArrayList<>();
if (checkPermissions()) {
// Permissions previously granted skip requesting them
parseArguments();
showSamples();
} else {
// Chain request permissions
requestNextPermission();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.main_menu, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
MenuItem checkable = menu.findItem(R.id.menu_benchmark_mode);
checkable.setChecked(isBenchmarkMode);
checkable = menu.findItem(R.id.menu_headless);
checkable.setChecked(isHeadless);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if(item.getItemId() == R.id.filter_button) {
sampleListView.dialog.show(getSupportFragmentManager(), "filter");
return true;
} else if(item.getItemId() == R.id.menu_run_samples) {
String category = "";
ViewPagerAdapter adapter = ((ViewPagerAdapter) sampleListView.viewPager.getAdapter());
if (adapter != null) {
category = adapter.getCurrentFragment().getCategory();
}
List<String> arguments = new ArrayList<>();
arguments.add("batch");
arguments.add("--category");
arguments.add(category);
arguments.add("--tag");
arguments.addAll(sampleListView.dialog.getFilter());
String[] sa = {};
launchWithCommandArguments(arguments.toArray(sa));
return true;
} else if(item.getItemId() == R.id.menu_benchmark_mode) {
isBenchmarkMode = !item.isChecked();
item.setChecked(isBenchmarkMode);
return true;
} else if(item.getItemId() == R.id.menu_headless) {
isHeadless = !item.isChecked();
item.setChecked(isHeadless);
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
requestNextPermission();
}
/**
* Load a native library
*
* @param native_lib_name Native library to load
* @return True if loaded correctly; False otherwise
*/
private boolean loadNativeLibrary(String native_lib_name) {
boolean status = true;
try {
System.loadLibrary(native_lib_name);
} catch (UnsatisfiedLinkError e) {
Toast.makeText(getApplicationContext(), "Native code library failed to load.", Toast.LENGTH_SHORT).show();
status = false;
}
return status;
}
/**
* Chain request permissions from the required permission list. When called the
* next unrequested permission with be requested If all permission are requested
* but not all granted; requested states will be reset and the PermissionView
* will be shown
*/
public void requestNextPermission() {
boolean granted = true;
for (Permission permission : permissions) {
// Permission previously requested but not granted
if (!permission.granted(getApplication())) {
// Permission not previously requested - request it
if (!permission.isRequested()) {
permission.request(this);
return;
}
granted = false;
}
}
if (granted) {
parseArguments();
showSamples();
} else {
// Reset all permissions request state so they can be requested again
for (Permission permission : permissions) {
permission.setRequested(false);
}
showPermissionsMessage();
}
}
/**
* Check all required permissions have been granted
*
* @return True if all granted; False otherwise
*/
public boolean checkPermissions() {
for (Permission permission : permissions) {
if (!permission.granted(getApplicationContext())) {
return false;
}
}
return true;
}
/**
* Show/Create the Permission View. Hides all other views
*/
public void showPermissionsMessage() {
if (permissionView == null) {
permissionView = new PermissionView(this);
}
if (sampleListView != null) {
sampleListView.hide();
}
permissionView.show();
}
/**
* Show/Create the Sample View. Hides all other views
*/
public void showSamples() {
if (sampleListView == null) {
sampleListView = new SampleListView(this);
}
if (permissionView != null) {
permissionView.hide();
}
sampleListView.show();
}
// Allow Orientation locking for testing
@SuppressLint("SourceLockedOrientationActivity")
public void parseArguments() {
// Handle argument passing
Bundle extras = this.getIntent().getExtras();
if (extras != null) {
if (extras.containsKey("cmd")) {
String[] commands = null;
String[] command_arguments = extras.getStringArray("cmd");
String command = extras.getString("cmd");
if (command_arguments != null) {
commands = command_arguments;
} else if (command != null) {
commands = command.split("[ ]+");
}
if (commands != null) {
for (String cmd : commands) {
if (cmd.equals("test")) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
break;
}
}
launchWithCommandArguments(commands);
finishAffinity();
}
} else if (extras.containsKey("sample")) {
launchSample(extras.getString("sample"));
finishAffinity();
} else if (extras.containsKey("test")) {
launchTest(extras.getString("test"));
finishAffinity();
}
}
}
/**
* Set arguments of the Native Activity You may also use a String[]
*
* @param args A string array of arguments
*/
public void setArguments(String... args) {
List<String> arguments = new ArrayList<>(Arrays.asList(args));
if (isBenchmarkMode) {
arguments.add("--benchmark");
}
if (isHeadless) {
arguments.add("--headless_surface");
}
String[] argArray = new String[arguments.size()];
sendArgumentsToPlatform(arguments.toArray(argArray));
}
public void launchWithCommandArguments(String... args) {
setArguments(args);
Intent intent = new Intent(SampleLauncherActivity.this, NativeSampleActivity.class);
startActivity(intent);
}
public void launchTest(String testID) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
launchWithCommandArguments("test", testID);
}
public void launchSample(String sampleID) {
Sample sample = samples.findByID(sampleID);
if (sample == null) {
Notifications.toast(this, "Could not find sample " + sampleID);
showSamples();
return;
}
launchWithCommandArguments("sample", sampleID);
}
/**
* Get samples from the Native Application
*
* @return A list of Samples that are currently installed in the Native
* Application
*/
private native Sample[] getSamples();
/**
* Set the arguments of the Native Application
*
* @param args The arguments that are to be passed to the app
*/
private native void sendArgumentsToPlatform(String[] args);
}
@@ -0,0 +1,104 @@
/* Copyright (c) 2019-2021, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* 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.khronos.vulkan_samples;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListView;
import com.khronos.vulkan_samples.model.Sample;
import java.util.ArrayList;
import java.util.List;
public class TabFragment extends Fragment {
private String category;
private List<Sample> sampleList = new ArrayList<>();
private AdapterView.OnItemClickListener clickListener;
/**
* Create a new Tab Fragment Instance
*
* @param category The Title of the TabFragment
* @param sampleList The Samples that this tab will render
* @param clickListener The listener used when a item is clicked
* @return A new Tab Fragment
*/
public static Fragment getInstance(String category, List<Sample> sampleList, AdapterView.OnItemClickListener clickListener) {
Bundle bundle = new Bundle();
bundle.putString("category", category);
TabFragment tabFragment = new TabFragment();
tabFragment.setArguments(bundle);
tabFragment.setSampleList(sampleList);
tabFragment.setClickListener(clickListener);
return tabFragment;
}
/**
* Set the sample list that the fragment should render
*
* @param list A list of samples
*/
public void setSampleList(List<Sample> list) {
this.sampleList = list;
}
/**
* Set the action to perform when a sample list item is clicked
*
* @param clickListener A OnItemClickListener
*/
public void setClickListener(AdapterView.OnItemClickListener clickListener) {
this.clickListener = clickListener;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
assert getArguments() != null;
category = getArguments().getString("category");
setRetainInstance(true);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_tab, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ListView listView = view.findViewById(R.id.sample_list);
SampleArrayAdapter sampleArrayAdapter = new SampleArrayAdapter(view.getContext(), sampleList);
listView.setAdapter(sampleArrayAdapter);
listView.setOnItemClickListener(this.clickListener);
}
public String getCategory() {
return category;
}
}
@@ -0,0 +1,105 @@
/* Copyright (c) 2019-2021, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* 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.khronos.vulkan_samples;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import android.view.ViewGroup;
import android.widget.AdapterView;
import com.khronos.vulkan_samples.model.Sample;
import com.khronos.vulkan_samples.model.SampleStore;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class ViewPagerAdapter extends FragmentStatePagerAdapter {
private TabFragment currentFragment;
private List<Sample> viewableSamples = new ArrayList<>();
private final SampleStore samples;
private final AdapterView.OnItemClickListener clickListener;
public ViewPagerAdapter(FragmentManager manager, SampleStore samples, AdapterView.OnItemClickListener clickListener) {
super(manager);
this.samples = samples;
this.clickListener = clickListener;
applyFilter(samples.getTags());
}
public void setDialog(FilterDialog dialog) {
dialog.setup(this, samples.getTags());
}
/**
* Modifies the filter so that the viewableSamples updates
*/
public void applyFilter(List<String> filterTags) {
viewableSamples = new ArrayList<>(samples.getByTags(filterTags));
}
@NonNull
@Override
public Fragment getItem(int position) {
List<Sample> fragmentSamples = new ArrayList<>();
String category = samples.getCategories().get(position);
assert category != null;
for (Sample sample : Objects.requireNonNull(samples.getByCategory(category))) {
for (Sample viewableSample : this.viewableSamples) {
if (sample.equals(viewableSample)) {
fragmentSamples.add(sample);
}
}
}
return TabFragment.getInstance(category, fragmentSamples, clickListener);
}
public int getItemPosition(@NonNull Object object) {
return POSITION_NONE;
}
@Override
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
if (getCurrentFragment() != object) {
currentFragment = (TabFragment) object;
}
super.setPrimaryItem(container, position, object);
}
@Override
public int getCount() {
return samples.getCategoryIndex().size();
}
@Override
public CharSequence getPageTitle(int position) {
return samples.getCategories().get(position);
}
TabFragment getCurrentFragment() {
return currentFragment;
}
}
@@ -0,0 +1,95 @@
/* Copyright (c) 2021, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* 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.khronos.vulkan_samples.common;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import android.widget.Toast;
import android.content.Context;
import com.khronos.vulkan_samples.R;
public class Notifications {
private static final String NOTIFICATION_PREFS = "NOTIFICATION_PREFS";
private static final String NOTIFICATION_KEY = "NOTIFICATION_KEY";
public static final String CHANNEL_ID = "vkb";
private static int initialId = 0;
public static void initNotificationChannel(Context context) {
// Create Notification Channel API 26+
//
// API 26+ implements notifications using a channel where notifications are submitted to channel
// The following initialises the channel used by vkb to push notifications to.
// IMPORTANCE_HIGH = push notifications
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_name), NotificationManager.IMPORTANCE_HIGH);
channel.setDescription(context.getString(R.string.channel_desc));
NotificationManager nm = context.getSystemService(NotificationManager.class);
nm.createNotificationChannel(channel);
}
}
public static int getNotificationId(Context context) {
SharedPreferences prefs = context.getSharedPreferences(NOTIFICATION_PREFS, Context.MODE_PRIVATE);
int id = initialId;
if (prefs.contains(NOTIFICATION_KEY)) {
id = prefs.getInt(NOTIFICATION_KEY, initialId);
}
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(NOTIFICATION_KEY, id + 1);
editor.apply();
return id;
}
/**
* Create a push notification from JNI with a message
*
* @param message A string of a message to be shown
*/
public static void notification(Context context, final String message) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.icon)
.setContentTitle("Vulkan Samples Error")
.setContentText(message)
.setStyle(new NotificationCompat.BigTextStyle()
.bigText(message))
.setPriority(NotificationCompat.PRIORITY_HIGH);
NotificationManagerCompat nm = NotificationManagerCompat.from(context);
nm.notify(getNotificationId(context), builder.build());
}
/**
* Create a toast from JNI on the android main UI thread with a message
*
* @param message A string of a message to be shown
*/
public static void toast(final Context context, final String message) {
new Handler(Looper.getMainLooper()).post(() -> Toast.makeText(context, message, Toast.LENGTH_LONG).show());
}
}
@@ -0,0 +1,96 @@
/* Copyright (c) 2021, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* 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.khronos.vulkan_samples.common;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Utils {
/**
* A helper to capitalize a string
*
* @param str The string to be capitalized
* @return A capitalized string
*/
public static String capitalize(String str) {
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
/**
* Convert List of Boolean to primitive boolean array
* @param list The list to be converted
* @return A primitive boolean array containing the lists values
*/
public static boolean[] toBoolArray(List<Boolean> list) {
boolean[] booleans = new boolean[list.size()];
for (int i = 0; i < list.size(); i++) {
booleans[i] = list.get(i);
}
return booleans;
}
/**
* Convert List of String to String array
* @param list The list to be converted
* @return A String array containing the lists values
*/
public static String[] toStringArray(List<String> list) {
String[] strings = new String[list.size()];
for (int i = 0; i < list.size(); i++) {
strings[i] = list.get(i);
}
return strings;
}
/**
* Comparator orders with a predefined list
*/
public static class PredefinedOrderComparator implements Comparator<String> {
private final List<String> defined_order;
public PredefinedOrderComparator(String... predefined_order) {
defined_order = Collections.unmodifiableList(Arrays.asList(predefined_order));
}
@Override
public int compare(String o1, String o2) {
// Remove duplicates
if (o1.equals(o2)) {
return 0;
}
int ret;
int o1_order = defined_order.indexOf(o1);
int o2_order = defined_order.indexOf(o2);
//if neither are found in the order list, then do natural sort - string comparison
if (o1_order < 0 && o2_order < 0) ret = o1.compareTo(o2);
//if only one is found in the order list, float it above the other
else if (o1_order < 0) ret = 1;
else if (o2_order < 0) ret = -1;
//if both are found in the order list, then do the index comparision
else ret = o1_order - o2_order;
return ret;
}
}
}
@@ -0,0 +1,91 @@
/* Copyright (c) 2021, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* 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.khronos.vulkan_samples.model;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
public class Permission {
private final String name;
private final int code;
private boolean requested = false;
public Permission(String name, int code) {
this.name = name;
this.code = code;
}
/**
* Set the requested state of the permission
*
* @param requested The desired requested state
*/
public void setRequested(boolean requested) {
this.requested = requested;
}
/**
* Query if the permission has already been requested
*
* @return True if previously requested; false otherwise
*/
public boolean isRequested() {
return requested;
}
/**
* Get the name of the permission
*
* @return The permissions name
*/
public String getName() {
return name;
}
/**
* Get the code of the permission
*
* @return The permissions code
*/
public int getCode() {
return code;
}
/**
* Query whether the permission has been granted for the App
*
* @param ctx The activity context that needs to be queried for the permission
* @return True if permission granted; false otherwise
*/
public boolean granted(Context ctx) {
return ContextCompat.checkSelfPermission(ctx, name) == PackageManager.PERMISSION_GRANTED;
}
/**
* Request the permission for a given Activity
*
* @param activity Activity to request the permission for
*/
public void request(Activity activity) {
ActivityCompat.requestPermissions(activity, new String[]{name}, code);
requested = true;
}
}
@@ -0,0 +1,164 @@
/* Copyright (c) 2019-2021, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* 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.khronos.vulkan_samples.model;
import android.text.TextUtils;
import com.khronos.vulkan_samples.common.Utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
public class Sample implements Comparable<Sample> {
private final String id;
private final String category;
private final String author;
private final String name;
private final String description;
private String tagText;
private final List<String> tags;
public Sample(String id, String category, String author, String name, String description, String[] tags) {
this.id = id;
this.category = category;
this.author = author;
this.name = name;
this.description = description;
this.tags = new LinkedList<>(Arrays.asList(tags));
}
/**
* Generate the Tag Text that is shown in the sample list
*
* @return A concatenated string of a samples tags
*/
private String generateTagText() {
List<String> formatted_tags = new ArrayList<>(this.tags.size());
for (String tag : this.tags) {
formatted_tags.add(Utils.capitalize(tag));
}
// If the sample has a tag other than "Any" then "Any" can be removed
if (formatted_tags.size() > 1) {
formatted_tags.remove("Any");
}
return TextUtils.join(", ", formatted_tags);
}
/**
* Get the Sample's ID
*
* @return A Sample ID
*/
public String getId() {
return id;
}
/**
* Get the sample's category
*
* @return The sample's category
*/
public String getCategory() {
return category;
}
/**
* Get the sample's author
*
* @return The sample's author
*/
public String getAuthor() {
return author;
}
/**
* Get the sample's name
*
* @return The sample's name
*/
public String getName() {
return name;
}
/**
* Get the sample's description
*
* @return The sample's description
*/
public String getDescription() {
return description;
}
/**
* Get the sample's tags
*
* @return The sample's tags
*/
public List<String> getTags() {
return tags;
}
/**
* Get the sample's tag text
*
* @return The sample's tag text
*/
public String getTagText() {
if (tagText == null) {
tagText = generateTagText();
}
return tagText;
}
/**
* Checks whether two samples are equal
* Can be used with a HashMap
*
* @return True if samples are equal; false otherwise
*/
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object instanceof Sample) {
Sample sample = (Sample) object;
return this.id.equals(sample.id);
}
return false;
}
/**
* Used when comparing two samples - Alphabetical Case Insensitive
* Sample id's are unique so this is used to compare
*
* @param sample To compare too
* @return - if less, 0 if equal + if greater than
*/
public int compareTo(Sample sample) {
return sample.id.compareToIgnoreCase(this.id);
}
}
@@ -0,0 +1,159 @@
/* Copyright (c) 2021, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* 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.khronos.vulkan_samples.model;
import com.khronos.vulkan_samples.common.Utils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
/**
* A one stop store of samples that are pre indexed for use in the App
*/
public class SampleStore {
private final Set<Sample> samples = new TreeSet<>();
private final Set<String> categories = new TreeSet<>(new Utils.PredefinedOrderComparator("api", "performance", "extensions"));
private final Map<String, List<Sample>> categoryIndex = new HashMap<>();
private final Set<String> tags = new TreeSet<>(new Utils.PredefinedOrderComparator("any"));
private final Map<String, List<Sample>> tagIndex = new HashMap<>();
public SampleStore(List<Sample> samples) {
if (samples == null) {
samples = new ArrayList<>();
}
// Index all samples by tag and by category
for (Sample sample : samples) {
if (sample == null) {
continue;
}
this.samples.add(sample);
if (!categoryIndex.containsKey(sample.getCategory())) {
categoryIndex.put(sample.getCategory(), new ArrayList<>());
}
List<Sample> category = categoryIndex.get(sample.getCategory());
if (category != null) {
category.add(sample);
}
for (String tag : sample.getTags()) {
if (!tagIndex.containsKey(tag)) {
tagIndex.put(tag, new ArrayList<>());
}
List<Sample> index = tagIndex.get(tag);
if (index != null) {
index.add(sample);
}
this.tags.add(tag);
}
this.categories.add(sample.getCategory());
}
}
/**
* Find a sample by its id
*
* @param id of a given sample
* @return A sample if it is found
*/
public Sample findByID(String id) {
for (Sample sample : samples) {
if (sample.getId().equals(id)) {
return sample;
}
}
return null;
}
/**
* Get the category index
*
* @return A map of category to samples
*/
public Map<String, List<Sample>> getCategoryIndex() {
return this.categoryIndex;
}
/**
* Get all category names
*
* @return A list of category names
*/
public List<String> getCategories() {
return new ArrayList<>(this.categories);
}
/**
* Get all tag names
*
* @return A list of tag names
*/
public List<String> getTags() {
return new ArrayList<>(this.tags);
}
/**
* Get a list of sample by tag name
*
* @return A list of samples
*/
public List<Sample> getByTag(String tag) {
return this.tagIndex.get(tag);
}
/**
* Get a list of sample by multiple tag names
*
* @return A list of samples
*/
public List<Sample> getByTags(List<String> tags) {
Set<Sample> samples = new HashSet<>();
for (String tag : tags) {
Collection<Sample> samplesByTag = getByTag(tag);
if (samplesByTag != null) {
samples.addAll(samplesByTag);
}
}
return new ArrayList<>(samples);
}
/**
* Get a list of sample by category name
*
* @return A list of samples
*/
public List<Sample> getByCategory(String category) {
return this.categoryIndex.get(category);
}
}
@@ -0,0 +1,70 @@
/* Copyright (c) 2021, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* 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.khronos.vulkan_samples.views;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.khronos.vulkan_samples.R;
import com.khronos.vulkan_samples.SampleLauncherActivity;
/**
* A container for all elements related to the permission view
*/
public class PermissionView {
private final Button buttonPermissions;
private final TextView textPermissions;
public PermissionView(SampleLauncherActivity activity) {
buttonPermissions = activity.findViewById(R.id.button_permissions);
buttonPermissions.setOnClickListener(new CheckPermissionClickListener(activity));
textPermissions = activity.findViewById(R.id.text_permissions);
}
/**
* Show the permission view
*/
public void show() {
buttonPermissions.setVisibility(View.VISIBLE);
textPermissions.setVisibility(View.VISIBLE);
}
/**
* Hide the permission view
*/
public void hide() {
buttonPermissions.setVisibility(View.INVISIBLE);
textPermissions.setVisibility(View.INVISIBLE);
}
}
/**
* Click listener for the Permission View button
*/
class CheckPermissionClickListener implements View.OnClickListener {
private final SampleLauncherActivity activity;
CheckPermissionClickListener(SampleLauncherActivity activity) {
this.activity = activity;
}
public void onClick(View v) {
activity.requestNextPermission();
}
}
@@ -0,0 +1,83 @@
/* Copyright (c) 2020-2021, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* 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.khronos.vulkan_samples.views;
import com.google.android.material.tabs.TabLayout;
import androidx.viewpager.widget.ViewPager;
import android.view.View;
import android.widget.AdapterView;
import com.khronos.vulkan_samples.FilterDialog;
import com.khronos.vulkan_samples.R;
import com.khronos.vulkan_samples.SampleLauncherActivity;
import com.khronos.vulkan_samples.ViewPagerAdapter;
import com.khronos.vulkan_samples.model.Sample;
/**
* A container for all elements related to the sample view
*/
public class SampleListView {
private final TabLayout tabLayout;
public ViewPager viewPager;
public FilterDialog dialog;
public SampleListView(SampleLauncherActivity activity) {
viewPager = activity.findViewById(R.id.viewpager);
ViewPagerAdapter adapter = new ViewPagerAdapter(activity.getSupportFragmentManager(), activity.samples, new SampleItemClickListener(activity));
viewPager.setAdapter(adapter);
dialog = new FilterDialog();
adapter.setDialog(dialog);
tabLayout = activity.findViewById(R.id.tabs);
tabLayout.setupWithViewPager(viewPager);
}
/**
* Show the sample view
*/
public void show() {
tabLayout.setVisibility(View.VISIBLE);
viewPager.setVisibility(View.VISIBLE);
}
/**
* Hide the sample view
*/
public void hide() {
tabLayout.setVisibility(View.INVISIBLE);
viewPager.setVisibility(View.INVISIBLE);
}
}
/**
* Click listener for a Sample List Item
* Start the Native Activity for the clicked Sample
*/
class SampleItemClickListener implements AdapterView.OnItemClickListener {
private final SampleLauncherActivity activity;
SampleItemClickListener(SampleLauncherActivity activity) {
this.activity = activity;
}
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String sampleID = ((Sample) parent.getItemAtPosition(position)).getId();
activity.launchSample(sampleID);
}
}