apply plugin: "kotlin-kapt"
// Google & Support
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
kapt "android.arch.lifecycle:compiler:1.1.1"
// Kotlin
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
// Kotlin Components
implementation 'android.arch.persistence.room:runtime:1.1.1'
kapt 'android.arch.persistence.room:compiler:1.1.1'
androidTestImplementation 'android.arch.persistence.room:testing:1.1.1'
implementation "android.arch.lifecycle:extensions:1.1.1"
implementation "android.arch.lifecycle:viewmodel:1.1.1"
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.8.0'
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
// Others
implementation 'com.github.bumptech.glide:glide:4.9.0'
kapt 'com.github.bumptech.glide:compiler:4.9.0'
// Test
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
// Google and Support
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.media:media:1.1.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.browser:browser:1.0.0'
implementation "android.arch.work:work-runtime:1.0.1"
implementation 'com.google.android.material:material:1.0.0'
implementation 'com.google.firebase:firebase-core:17.2.0'
implementation 'com.google.firebase:firebase-perf:19.0.0'
implementation 'com.google.android.gms:play-services-ads-lite:18.2.0'
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
annotationProcessor 'androidx.annotation:annotation:1.1.0'
// Others
implementation 'com.karumi:dexter:4.2.0'
implementation 'com.kyleduo.switchbutton:library:1.4.5'
implementation 'com.github.bumptech.glide:glide:4.9.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
// For Improvement
implementation 'com.github.anrwatchdog:anrwatchdog:1.4.0'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.2'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.2'
// Test
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
<style name="Toolbar.Popup" parent="ThemeOverlay.AppCompat">
<item name="android:colorForeground">?attr/colorThemedContent</item>
<item name="android:colorBackground">?colorPrimaryDark</item>
</style>
<style name="Toolbar.Theme" parent="AppTheme.Dark">
<item name="android:textColorSecondary">@color/colorWhite</item>
</style>
<style name="MyDialog" parent="@android:style/Theme.Dialog">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowNoTitle">true</item>
<!--<item name="android:windowIsTranslucent">true</item>-->
<!--<item name="android:padding">5dp</item>-->
<!--<item name="android:backgroundDimEnabled">false</item>-->
<!--<item name="android:windowIsFloating">false</item>-->
</style>
<style name="PopupMenuStyle" parent="ThemeOverlay.AppCompat.Dark">
<!-- if using android.widget.PopupMenu -->
<!--<item name="android:popupMenuStyle">@style/PopupMenu</item>-->
<!-- if using android.support.v7.widget.PopupMenu -->
<item name="popupMenuStyle">@style/Base.PopupMenuStyle</item>
</style>
<style name="Base.PopupMenuStyle" parent="Base.Widget.AppCompat.PopupMenu">
<item name="android:textColor">@android:color/white</item>
<item name="android:popupBackground">@drawable/bg_translucent_outlined</item>
<item name="android:actionBarWidgetTheme">@style/MyActionBarWidgetTheme</item>
</style>
<style name="MyActionBarWidgetTheme" parent="@android:style/Theme.Holo">
<item name="android:checkboxStyle">@style/checkBoxStyle</item>
</style>
<style name="checkBoxStyle" parent="Base.Theme.AppCompat">
<item name="colorAccent">@color/colorPrimary_lt</item>
<item name="android:textColorSecondary">@color/colorPrimary_lt</item>
</style>
<style name="PlayerTheme" parent="Theme.AppCompat.NoActionBar">
<item name="colorPrimary">@color/colorPrimary_lt</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark_lt</item>
<item name="colorAccent">@color/colorLightBlue</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
</style>
// Fragment
public class InfoFragment extends Fragment {
private static final String TYPE = "type";
private static final String INFO_SHORT = "info_message";
private static final String INFO_LONG = "info_slide_show";
public static final int ERROR = 1;
public static final int NOT_FOUND = 2;
public static final int DATA_LOADING = 3;
public static final int PERMISSION_ERROR = 4;
private FragmentInfoBinding binding;
private WeakReference<MainActivity> activity;
private String infoShort;
private String infoLong;
private int type;
private Animation zoomIn;
private Animation zoomOut;
public static InfoFragment getInstance(int type
, String infoShort
, String infoLong) {
InfoFragment fragment = new InfoFragment();
Bundle args = new Bundle();
args.putInt(TYPE, type);
args.putString(INFO_SHORT, infoShort);
args.putString(INFO_LONG, infoLong);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
if (args != null) {
type = args.getInt(TYPE);
infoShort = args.getString(INFO_SHORT);
infoLong = args.getString(INFO_LONG);
}
activity = new WeakReference<>((MainActivity) getActivity());
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater
, ViewGroup container
, Bundle savedInstanceState) {
binding = DataBindingUtil.inflate(inflater
, R.layout.fragment_info
, container
, false);
zoomIn = AnimationUtils.loadAnimation(getActivity(), R.anim.zoom_in);
zoomOut = AnimationUtils.loadAnimation(getActivity(), R.anim.zoom_out);
if (type != DATA_LOADING) {
switch (type) {
case ERROR:
binding.ivInfo.setImageResource(R.drawable.ic_error);
break;
case NOT_FOUND:
binding.ivInfo.setImageDrawable(DrawableUtil
.setTint(activity
.get()
.getResources()
.getDrawable(R.drawable.ic_not_found)
, activity.get().getThemeContentColor()));
break;
case PERMISSION_ERROR:
binding.ivInfo.setImageResource(R.drawable.ic_error);
binding.btnAskPermission.setVisibility(View.VISIBLE);
setAskButtonClickListener();
break;
}
binding.ivInfo.startAnimation(zoomIn);
setAnimationListener();
}
binding.tvInfoShort.setText(infoShort != null ? infoShort : "");
binding.tvInfoLong.setText(infoLong != null ? infoLong : "");
return binding.getRoot();
}
private void setAnimationListener() {
zoomIn.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
binding.ivInfo.startAnimation(zoomOut);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
zoomOut.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
binding.ivInfo.startAnimation(zoomIn);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
private void setAskButtonClickListener() {
binding.btnAskPermission.setOnClickListener(view -> activity.get().askForReadWritePermission());
}
@Override
public void onResume() {
super.onResume();
}
}
// Adapter
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_1 = 1;
private static final int TYPE_2 = 2;
private WeakReference<AppCompatActivity> activity;
private List<Object> objects;
private SparseBooleanArray selectedItems;
private MyAdapter.OnItemTouchListener onItemTouchListener;
/////////////////////////////////////////////////////////////////////
/////// Adapter Lifecycle Methods
/////////////////////////////////////////////////////////////////////
public MyAdapter(AppCompatActivity activity
, List<Object> objects) {
this.activity = new WeakReference<>(activity);
selectedItems = new SparseBooleanArray();
if (objects != null) {
this.objects = new ArrayList<>(objects);
}
}
@Override
public int getItemCount() {
return objects != null ? objects.size() + 1 : 0;
}
@Override
public int getItemViewType(int position) {
return position == 0 ? TYPE_1 : TYPE_2;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
switch (i) {
case TYPE_1: {
ItemListInfoBinding binding = DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()),
R.layout.item_type_1
, viewGroup
, false);
return new InfoViewHolder(binding);
}
default: {
ItemObjectBinding binding = DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()),
R.layout.item_type_2
, viewGroup
, false);
return new ObjectViewHolder(binding);
}
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int pos) {
int position = viewHolder.getAdapterPosition();
RelativeLayout lytParent;
switch (viewHolder.getItemViewType()) {
case TYPE_1: {
}
break;
default: {
}
adjustItemParams((ObjectViewHolder) viewHolder, position);
}
lytParent.setOnClickListener(v -> {
if (onItemTouchListener != null) {
onItemTouchListener.onClick(position);
}
});
lytParent.setOnLongClickListener(v -> {
if (onItemTouchListener != null) {
onItemTouchListener.onLongClick(position);
}
return true;
});
// setAnimation(viewHolder.itemView, position);
}
private void adjustItemParams(ObjectViewHolder ObjectViewHolder, int position) {
AppCompatActivity activity = this.activity.get();
if (activity != null) {
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT
, ViewGroup.LayoutParams.WRAP_CONTENT
);
int dp0 = (int) ScreenUtil.convertDpToPixel(activity, 0);
int dp2 = (int) ScreenUtil.convertDpToPixel(activity, 2);
if (getItemCount() > 1 && position == getItemCount() - 1) {
layoutParams.bottomMargin = dp2;
} else {
layoutParams.bottomMargin = dp0;
}
ObjectViewHolder.binding.lytParent.setLayoutParams(layoutParams);
}
}
private void setAnimation(View viewToAnimate, int position) {
if (position > lastPosition) {
Animation animation = AnimationUtils.loadAnimation(activity.get(), android.R.anim.fade_in);
viewToAnimate.startAnimation(animation);
lastPosition = position;
}
}
/////////////////////////////////////////////////////////////////////
/////// View Holders
/////////////////////////////////////////////////////////////////////
class InfoViewHolder extends RecyclerView.ViewHolder {
ItemListInfoBinding binding;
InfoViewHolder(ItemListInfoBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
void setItemSelected() {
AppCompatActivity activity = MyAdapter.this.activity.get();
if (activity != null) {
this.binding.lytParent
.setBackground(ThemeUtil
.resolveThemedDrawable(activity, R.attr.bkSelectedItem));
}
}
void setItemDeselected() {
AppCompatActivity activity = MyAdapter.this.activity.get();
if (activity != null) {
TypedValue outValue = new TypedValue();
activity
.getTheme()
.resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);
binding.lytParent.setBackgroundResource(outValue.resourceId);
}
}
}
class ObjectViewHolder extends RecyclerView.ViewHolder {
ItemObjectBinding binding;
ObjectViewHolder(ItemObjectBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
void setItemSelected() {
AppCompatActivity activity = MyAdapter.this.activity.get();
if (activity != null) {
this.binding.lytParent
.setBackground(ThemeUtil
.resolveThemedDrawable(activity, R.attr.bkSelectedItem));
}
}
void setItemDeselected() {
AppCompatActivity activity = MyAdapter.this.activity.get();
if (activity != null) {
TypedValue outValue = new TypedValue();
activity
.getTheme()
.resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);
binding.lytParent.setBackgroundResource(outValue.resourceId);
}
}
}
/////////////////////////////////////////////////////////////////////
/////// Methods related to MediaItems Selection
/////////////////////////////////////////////////////////////////////
public void toggleItemSelection(int position) {
if (selectedItems.get(position, false)) {
selectedItems.delete(position);
if (getSelectedItemCount() == getobjectsCount() - 1) {
resetInfoItemFlag();
}
} else {
selectedItems.put(position, true);
if (getSelectedItemCount() == getobjectsCount()) {
resetInfoItemFlag();
}
}
notifyItemChanged(position);
}
public void selectAllItems() {
resetInfoItem = true;
for (int i = 1; i <= objects.size(); i++) {
if (!selectedItems.get(i, false)) {
selectedItems.put(i, true);
}
}
notifyDataSetChanged();
}
public void clearSelections() {
resetInfoItem = true;
selectedItems.clear();
notifyDataSetChanged();
}
public int getSelectedItemCount() {
return selectedItems.size();
}
public List<Integer> getSelectedItems() {
List<Integer> items = new ArrayList<>(selectedItems.size());
for (int i = 0; i < selectedItems.size(); i++) {
items.add(selectedItems.keyAt(i) - 1);
}
return items;
}
/////////////////////////////////////////////////////////////////////
/////// Public Methods
/////////////////////////////////////////////////////////////////////
public void resetInfoItemFlag() {
this.resetInfoItem = true;
notifyItemChanged(0);
}
public int getobjectsCount() {
return objects != null ? objects.size() : 0;
}
public void removeItem(int position) {
objects.remove(position);
notifyItemRemoved(position);
}
public List<Object> getobjects() {
return objects;
}
public void setOnItemTouchListener(MyAdapter.OnItemTouchListener listener) {
onItemTouchListener = listener;
}
/////////////////////////////////////////////////////////////////////
/////// Recycler ItemTouchListener
/////////////////////////////////////////////////////////////////////
public interface OnItemTouchListener {
void onClick(int position);
void onLongClick(int position);
}
}a
// Service
private ServiceConnection workerServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
WorkerService.LocalBinder binder = (WorkerService.LocalBinder) service;
workerService = binder.getService();
workerService.setOnDataLoadedListener(recordUpdated -> {
if (ApiVersionUtil.isOreo() && isWorkerServiceBound) {
workerService.setOnDataLoadedListener(null);
unbindService(workerServiceConnection);
isWorkerServiceBound = false;
}
binding.appBarMain.contentMain.prgLoading.setVisibility(View.GONE);
Fragment directoriesFragment = fragmentManager.findFragmentByTag(FragmentTags.DIRECTORIES_FRAGMENT);
if (directoriesFragment != null
&& directoriesFragment.isVisible()) {
((DirectoriesFragment) directoriesFragment).finishSwipeRefresh();
}
if (recordUpdated
&& MainActivity.activityState == _BaseActivity.STATE_RESUMED) {
loadFragmentBasedOnData(-1);
}
});
isWorkerServiceBound = true;
}
public void onServiceDisconnected(ComponentName arg0) {
isWorkerServiceBound = false;
}
};
public class WorkerService extends Service {
// Service Handlers Messages
private static final int MSG_LOAD_DATA = 2345;
private static final int MSG_LOAD_THUMBNAILS = 2355;
// UI Handler Messages
private static final int MSG_INVOKE_ON_DATA_LOADED = 3145;
// Schedule Times in Seconds
private static final int TIME_LOAD_DATA = 60 * 1000;
// Fields related to the service
private WorkerService.LocalBinder binder = new WorkerService.LocalBinder();
private OnDataLoadedListener onDataLoadedListener;
private ServiceHandler serviceHandler;
private ThumbnailsHandler thumbnailsHandler;
private UiHandler uiHandler;
private DataSource dataSource;
/////////////////////////////////////////////////////////////////////
/////// Service lifecycle Methods
/////////////////////////////////////////////////////////////////////
@Override
public void onCreate() {
HandlerThread workerThread = new HandlerThread("WorkerThread", Process.THREAD_PRIORITY_BACKGROUND);
workerThread.start();
Looper workerThreadLooper = workerThread.getLooper();
serviceHandler = new ServiceHandler(workerThreadLooper, this);
HandlerThread thumbnailsThread = new HandlerThread("ThumbnailsThread", Process.THREAD_PRIORITY_BACKGROUND);
thumbnailsThread.start();
Looper thumbnailsThreadLooper = thumbnailsThread.getLooper();
thumbnailsHandler = new ThumbnailsHandler(thumbnailsThreadLooper, this);
uiHandler = new UiHandler(Looper.getMainLooper(), this);
dataSource = new DataSource(getApplicationContext());
if (ApiVersionUtil.isOreo()) {
createNotificationChannel();
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (ApiVersionUtil.isOreo()) {
startForeground(hashCode(), buildNotification(NotificationType.DataLoading));
}
if (intent != null) {
if (ApiVersionUtil.isOreo()) {
Message message = serviceHandler.obtainMessage(MSG_LOAD_DATA);
serviceHandler.sendMessage(message);
} else if (intent.getBooleanExtra(Constants.INTENT_RESET_DATA_LOAD_TIME_FLAG, false)) {
serviceHandler.removeMessages(MSG_LOAD_DATA);
Message message = serviceHandler.obtainMessage(MSG_LOAD_DATA);
message.arg1 = 1;
serviceHandler.sendMessage(message);
}
}
return ApiVersionUtil.isOreo() ? START_NOT_STICKY : START_REDELIVER_INTENT;
}
@Override
public void onDestroy() {
super.onDestroy();
}
/////////////////////////////////////////////////////////////////////
/////// Code related to the Binding
/////////////////////////////////////////////////////////////////////
public class LocalBinder extends Binder {
public WorkerService getService() {
return WorkerService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
public void setOnDataLoadedListener(OnDataLoadedListener listener) {
onDataLoadedListener = listener;
if (listener != null && !serviceHandler.hasMessages(MSG_LOAD_DATA)) {
Message message = serviceHandler.obtainMessage(MSG_LOAD_DATA);
message.arg1 = 1;
serviceHandler.sendMessage(message);
}
}
/////////////////////////////////////////////////////////////////////
////////// Permission Related Methods
/////////////////////////////////////////////////////////////////////
private boolean readWritePermissionsGranted() {
return ContextCompat.checkSelfPermission(this
, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
|| ContextCompat.checkSelfPermission(this
, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
}
/////////////////////////////////////////////////////////////////////
/////// Handlers to handle Messages received.
/////////////////////////////////////////////////////////////////////
private final class ServiceHandler extends Handler {
private WeakReference<WorkerService> weakReference;
ServiceHandler(Looper looper, WorkerService workerService) {
super(looper);
this.weakReference = new WeakReference<>(workerService);
}
@SuppressWarnings("unchecked")
@Override
public void handleMessage(Message msg) {
WorkerService workerService = weakReference.get();
switch (msg.what) {
case MSG_LOAD_DATA: {
if (readWritePermissionsGranted()) {
workerService.loadMediaData(msg.arg1 == 1);
} else if (ApiVersionUtil.isOreo()) {
if (SystemUtil.isServiceRunningInForeground(WorkerService.this, WorkerService.class)) {
workerService.stopForeground(true);
}
stopSelf();
}
if (!ApiVersionUtil.isOreo() && !serviceHandler.hasMessages(MSG_LOAD_DATA)) {
sendEmptyMessageDelayed(MSG_LOAD_DATA, TIME_LOAD_DATA);
}
}
break;
}
}
}
private final class ThumbnailsHandler extends Handler {
private WeakReference<WorkerService> weakReference;
ThumbnailsHandler(Looper looper, WorkerService workerService) {
super(looper);
this.weakReference = new WeakReference<>(workerService);
}
@SuppressWarnings("unchecked")
@Override
public void handleMessage(Message msg) {
WorkerService workerService = weakReference.get();
switch (msg.what) {
case MSG_LOAD_THUMBNAILS: {
List<MediaItem> mediaItems = (List<MediaItem>) msg.obj;
if (mediaItems != null) {
ThumbnailLoader thumbnailLoader = new ThumbnailLoader(workerService);
thumbnailLoader.loadAllMediaThumbnail(mediaItems);
}
}
}
}
}
private final class UiHandler extends Handler {
private WeakReference<WorkerService> weakReference;
UiHandler(Looper looper, WorkerService workerService) {
super(looper);
this.weakReference = new WeakReference<>(workerService);
}
@Override
public void handleMessage(Message msg) {
WorkerService workerService = weakReference.get();
switch (msg.what) {
case MSG_INVOKE_ON_DATA_LOADED: {
if (SystemUtil.isServiceRunningInForeground(WorkerService.this, WorkerService.class)) {
workerService.stopForeground(true);
}
if (ApiVersionUtil.isOreo()) {
if (workerService.onDataLoadedListener != null) {
workerService.onDataLoadedListener.onDataLoaded(msg.arg1 == 1);
}
stopSelf();
} else if (workerService.onDataLoadedListener != null) {
workerService.onDataLoadedListener.onDataLoaded(msg.arg1 == 1);
} else {
Message message = uiHandler.obtainMessage(MSG_INVOKE_ON_DATA_LOADED);
message.arg1 = msg.arg1;
uiHandler.sendMessage(message);
}
}
break;
}
}
}
/////////////////////////////////////////////////////////////////////
/////// Handler Messages related Methods
/////////////////////////////////////////////////////////////////////
public void loadMediaData(boolean resetDataLoadTime) {
boolean dataLoadingForFirstTime = !dataSource.checkIfMediaExists();
boolean recordUpdated;
if (!ApiVersionUtil.isOreo() && dataLoadingForFirstTime) {
startForeground(hashCode(), buildNotification(NotificationType.DataLoading));
}
DirectoryLoader directoryLoader = new DirectoryLoader(this);
MediaLoader mediaLoader = new MediaLoader(this);
List<Directory> directories;
List<MediaItem> mediaItems;
mediaItems = mediaLoader.fetchAllMedia();
directories = directoryLoader.fetchAllDirectoriesByMedia(mediaItems);
if (!thumbnailsHandler.hasMessages(MSG_LOAD_THUMBNAILS)) {
Message message = thumbnailsHandler.obtainMessage(MSG_LOAD_THUMBNAILS);
message.obj = mediaItems;
thumbnailsHandler.sendMessage(message);
}
dataSource.insertDirectories(directories);
recordUpdated = dataSource.insertMedia(mediaItems);
StringBuilder mediaIds = new StringBuilder();
for (int i = 0; i < mediaItems.size(); i++) {
MediaItem mediaItem = mediaItems.get(i);
mediaIds.append(mediaItem.getId());
if (i < mediaItems.size() - 1) {
mediaIds.append(",");
}
}
if (!recordUpdated) {
recordUpdated = dataSource.deleteExpiredMediaItems(mediaIds.toString());
} else {
dataSource.deleteExpiredMediaItems(mediaIds.toString());
}
if (!recordUpdated) {
recordUpdated = dataSource.deleteExpiredDirectories() > 0;
} else {
dataSource.deleteExpiredDirectories();
}
if ((dataLoadingForFirstTime || resetDataLoadTime || ApiVersionUtil.isOreo())
&& !uiHandler.hasMessages(MSG_INVOKE_ON_DATA_LOADED)) {
Message message = uiHandler.obtainMessage(MSG_INVOKE_ON_DATA_LOADED);
message.arg1 = recordUpdated ? 1 : 0;
uiHandler.sendMessage(message);
}
if (!ApiVersionUtil.isOreo() && resetDataLoadTime) {
serviceHandler.removeMessages(MSG_LOAD_DATA);
serviceHandler.sendEmptyMessageDelayed(MSG_LOAD_DATA, TIME_LOAD_DATA);
}
}
/////////////////////////////////////////////////////////////////////
////////// Methods related to Notifications
/////////////////////////////////////////////////////////////////////
private Notification buildNotification(NotificationType type) {
Notification notification;
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, BuildConfig.APPLICATION_ID);
builder.setSmallIcon(R.drawable.ic_play);
switch (type) {
case DataLoading: {
builder.setContentTitle(getResources().getString(R.string.app_name))
.setContentText("Player is loading media files.");
}
}
notification = builder.build();
return notification;
}
@RequiresApi(api = Build.VERSION_CODES.O)
private void createNotificationChannel() {
String channelId = BuildConfig.APPLICATION_ID;
CharSequence channelName = "APlayer Channel";
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel notificationChannel = new NotificationChannel(channelId, channelName, importance);
notificationChannel.enableLights(true);
notificationChannel.setLightColor(Color.BLUE);
notificationChannel.enableVibration(false);
notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager != null) {
notificationManager.createNotificationChannel(notificationChannel);
}
}
/////////////////////////////////////////////////////////////////////
////////// Listener Interfaces
/////////////////////////////////////////////////////////////////////
public interface OnDataLoadedListener {
void onDataLoaded(boolean recordUpdated);
}
}
// Custom Dialog Fragment
public class AlertDialog extends DialogFragment {
private DialogAlertBinding binding;
private WeakReference<AppCompatActivity> activity;
private IAlertDialogActionListener listener;
private boolean shown = false;
private String title = "";
private String message = "";
private int themeContentColor;
private int themeBackgroundColor;
private int icon;
private boolean translucent = false;
private boolean onlyOk = false;
private boolean cancelable = true;
/////////////////////////////////////////////////////////////////////
////////// AlertDialog Builder
/////////////////////////////////////////////////////////////////////
public static class Builder {
private AlertDialog alertDialog;
private Builder() {
}
public static Builder with(AppCompatActivity activity) {
Builder builder = new Builder();
builder.alertDialog = new AlertDialog();
builder.alertDialog.activity = new WeakReference<>(activity);
return builder;
}
public Builder setTitle(String title) {
this.alertDialog.title = title;
return this;
}
public Builder setMessage(String message) {
this.alertDialog.message = message;
return this;
}
public Builder setIconDrawable(int icon) {
this.alertDialog.icon = icon;
return this;
}
public Builder setThemeContentColor(int color) {
this.alertDialog.themeContentColor = color;
return this;
}
public Builder setThemeBackgroundColor(int color) {
this.alertDialog.themeBackgroundColor = color;
return this;
}
public Builder setTranslucent(boolean translucent) {
this.alertDialog.translucent = translucent;
return this;
}
public Builder setOnlyOk(boolean onlyOk) {
this.alertDialog.onlyOk = onlyOk;
return this;
}
public Builder setCancelable(boolean cancelable) {
this.alertDialog.cancelable = cancelable;
return this;
}
public Builder setActionListener(IAlertDialogActionListener listener) {
this.alertDialog.listener = listener;
return this;
}
public AlertDialog build() {
return alertDialog;
}
public void show() {
if(this.alertDialog.shown){
return;
}
FragmentManager manager = this.alertDialog.activity.get().getSupportFragmentManager();
manager
.beginTransaction()
.add(alertDialog, FragmentTags.ALERT_DIALOG_FRAGMENT)
.commitAllowingStateLoss();
}
}
/////////////////////////////////////////////////////////////////////
////////// Dialog Fragment lifecycle Methods
/////////////////////////////////////////////////////////////////////
@Override
public View onCreateView(@NonNull LayoutInflater inflater
, @Nullable ViewGroup container
, Bundle savedInstanceState) {
binding = DataBindingUtil.inflate(inflater
, R.layout.dialog_alert
, container
, false);
Window window = getDialog().getWindow();
if (window != null) {
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
}
setupViewsClickListeners();
setupUI();
return binding.getRoot();
}
@Override
public void onDetach() {
super.onDetach();
}
@Override
public void show(FragmentManager manager, String tag) {
if (shown)
return;
shown = true;
super.show(manager, tag);
}
@Override
public void onDismiss(DialogInterface dialog) {
shown = false;
super.onDismiss(dialog);
if (listener != null) {
listener.onDismiss();
}
}
public void show() {
if (shown || activity == null) {
return;
}
FragmentManager manager = activity.get().getSupportFragmentManager();
show(manager, FragmentTags.ALERT_DIALOG_FRAGMENT);
}
/////////////////////////////////////////////////////////////////////
////////// Alert Dialog specific Methods
/////////////////////////////////////////////////////////////////////
private void setupUI() {
if (activity == null) {
return;
}
binding.tvTitle.setText(title);
binding.tvMessage.setText(message);
if (themeContentColor != 0) {
if (icon != 0) {
binding.ivIcon
.setImageDrawable(DrawableUtil
.setTint(ContextCompat.getDrawable(activity.get()
, icon), themeContentColor
));
} else {
binding.ivIcon.setVisibility(View.GONE);
}
binding.tvTitle.setTextColor(themeContentColor);
binding.tvMessage.setTextColor(themeContentColor);
binding.btnOk.setTextColor(themeContentColor);
binding.btnCancel.setTextColor(themeContentColor);
}
if (themeBackgroundColor != 0) {
binding.lytParent.setBackground(
DrawableUtil.setTint(
getResources().getDrawable(R.drawable.bg_dialog)
, themeBackgroundColor
)
);
} else if (translucent) {
binding.lytParent.setBackgroundResource(R.drawable.bg_translucent_outlined);
}
if (onlyOk) {
binding.btnCancel.setVisibility(View.GONE);
}
setCancelable(cancelable);
}
public void setupViewsClickListeners() {
binding.btnOk.setOnClickListener(view -> {
if (listener != null) {
listener.onSelectOk();
}
dismissAllowingStateLoss();
});
binding.btnCancel.setOnClickListener(view -> {
if (listener != null) {
listener.onSelectCancel();
}
dismissAllowingStateLoss();
});
}
public boolean isShowing() {
return shown;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
binding.tvTitle.setText(title);
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
binding.tvMessage.setText(message);
}
/////////////////////////////////////////////////////////////////////
////////// Alert Dialog actions listener
/////////////////////////////////////////////////////////////////////
public interface IAlertDialogActionListener {
void onSelectOk();
void onSelectCancel();
void onDismiss();
}
}
// Room Database
@Dao
interface AppDao {
@Insert
fun insertApp(app: App)
@Update
fun updateApp(app: App)
@Query("DELETE FROM tbl_app")
fun deleteAll()
@Query("SELECT * FROM tbl_app WHERE package_name = :packageName")
fun getAppByPackage(packageName: String): App?
@Query("SELECT * FROM tbl_app WHERE size != 0.0 ORDER BY app_tab_type_id ASC")
fun getAllAppsAsc(): List<App>
@Query("SELECT * FROM tbl_app WHERE size != 0.0 ORDER BY app_tab_type_id DESC")
fun getAllAppsDesc(): List<App>
}
@Entity(tableName = "tbl_app", indices = [Index("package_name", unique = true)])
data class App(
@PrimaryKey
@ColumnInfo(name = "_id")
var id: Int = 0
, @ColumnInfo(name = "record_no")
var record_no: Int = 0
, @ColumnInfo(name = "type_id")
var typeId: Int = 0
, @ColumnInfo(name = "name")
var name: String
, @ColumnInfo(name = "file_name")
var fileName: String
, @ColumnInfo(name = "package_name")
var packageName: String
, @ColumnInfo(name = "description")
var description: String
, @ColumnInfo(name = "apk_sync_status")
var apkSyncStatus: Boolean
, @ColumnInfo(name = "size")
var size: Double
, @ColumnInfo(name = "version")
var version: Int
, @ColumnInfo(name = "app_tab_type_id")
var appTabTypeId: Int
, @Ignore
var installedVersion: Int
, @Ignore
var downloadStatus: Status
, @Ignore
var downloadProgress: Int
, @Ignore
var downloadErrorMessage: String?
, @Ignore
var signaturesMatched: Boolean?
) {
enum class Status {
NOT_INSTALLED, PENDING_INSTALL, NOT_UPDATED, PENDING_UPDATE, UPDATED
}
constructor() : this(
0
, 0
, 0
, ""
, ""
, ""
, ""
, false
, 0.0
, -1
, 1
, -1
, Status.UPDATED
, -1
, null
, false
)
}
@Database(entities = [App::class], version = Constants.DATABASE_VERSION)
abstract class UpdaterDatabase : RoomDatabase() {
companion object {
private var instance: UpdaterDatabase? = null
fun getInstance(context: Context): UpdaterDatabase? {
if (instance == null) {
synchronized(UpdaterDatabase::class) {
instance = Room.databaseBuilder(
context.applicationContext,
UpdaterDatabase::class.java,
Constants.DATABASE_NAME
).build()
}
}
return instance
}
fun destroyInstance() {
instance = null
}
}
abstract fun getAppDao(): AppDao
}
// Data Repository
class DataRepository private constructor(val context: Context) {
private val cTag: String = ">>>>> ${this::class.simpleName}"
private var weakReference: WeakReference<Context> = WeakReference(context)
private val appPreferences: AppPreferences?
private val database: UpdaterDatabase?
private var appsList: MutableLiveData<List<App>?> = MutableLiveData()
private var iconsDownloaded: MutableLiveData<Boolean> = MutableLiveData()
private var appCurrentlyDownloading: MutableLiveData<App> = MutableLiveData()
companion object : SingletonUtil<DataRepository, Context>(::DataRepository)
init {
database = UpdaterDatabase.getInstance(weakReference.get()!!)
appPreferences = AppPreferences(weakReference.get()!!)
}
/////////////////////////////////////////////////////////////////////
/////// Preferences Related Methods
/////////////////////////////////////////////////////////////////////
fun prefGetTabType(): Int {
return appPreferences!!.getTabType()
}
fun prefSetTabType(typeId: Int) {
return appPreferences!!.setTabType(typeId)
}
/////////////////////////////////////////////////////////////////////
/////// Database Related Methods
/////////////////////////////////////////////////////////////////////
fun dbInsertOrUpdateApp(app: App) {
val application = database?.getAppDao()?.getAppByPackage(app.packageName)
if (application == null) {
database?.getAppDao()?.insertApp(app)
} else {
database?.getAppDao()?.updateApp(app)
}
}
fun dbGetAllApps(tabType: Int): List<App>? {
return if (tabType == Constants.TAB_TEACHER) {
database?.getAppDao()?.getAllAppsAsc()
} else {
database?.getAppDao()?.getAllAppsDesc()
}
}
/////////////////////////////////////////////////////////////////////
/////// LiveData Related Methods
/////////////////////////////////////////////////////////////////////
fun liveSetAllApps(appsList: List<App>) {
this.appsList.postValue(appsList)
}
fun liveGetAllApps(): LiveData<List<App>?> {
return this.appsList
}
fun liveSetIconsDownloaded(iconsDownloaded: Boolean) {
this.iconsDownloaded.postValue(iconsDownloaded)
}
fun liveGetIconsDownloaded(): LiveData<Boolean> {
return this.iconsDownloaded
}
fun liveSetCurrentDownloadingApp(app: App) {
this.appCurrentlyDownloading.postValue(app)
}
fun liveGetCurrentDownloadingApp(): LiveData<App> {
return this.appCurrentlyDownloading
}
}
// ViewModel
class FragmentAppsViewModel(application: Application) : AndroidViewModel(application) {
private val dataRepository = DataRepository.getInstance(application)
/////////////////////////////////////////////////////////////////////
/////// Preference related Methods
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////// Database related Methods
/////////////////////////////////////////////////////////////////////
fun getAllApps(): LiveData<List<App>?> {
return dataRepository.liveGetAllApps()
}
/////////////////////////////////////////////////////////////////////
/////// Network related Methods
/////////////////////////////////////////////////////////////////////
fun checkIfIconsDownloaded(): LiveData<Boolean> {
return dataRepository.liveGetIconsDownloaded()
}
fun getCurrentlyDownloadingApp(): LiveData<App> {
return dataRepository.liveGetCurrentDownloadingApp()
}
}
// ViewModel Usage
viewModel = ViewModelProviders.of(this).get(FragmentAppsViewModel::class.java)
viewModel?.getAllApps()?.observe(this, Observer {
activity.get()?.dismissLoaderDialog()
if (it != null && it.isNotEmpty()) {
adapter = activity.get()?.let { a -> AppsAdapter(a, it.toMutableList()) }
adapter?.setOnItemTouchListener(object : AppsAdapter.OnItemTouchListener {
override fun onClick(position: Int, app: App) {
if (adapter?.checkIfAppIsCurrentlyDownloading(app.id) == false
&& (app.downloadStatus == App.Status.NOT_INSTALLED
|| app.downloadStatus == App.Status.NOT_UPDATED)
) {
adapter?.changeAppDownloadingStatus(app.id, true)
app.downloadProgress = 0
activity.get()?.downloadApk(app)
} else if (app.downloadStatus == App.Status.PENDING_INSTALL) {
currentlyInstallingApp = app
installApp(app)
} else if (app.downloadStatus == App.Status.PENDING_UPDATE) {
ValidateSignaturesAndUpdateTask(WeakReference(this@AppsFragment)).execute(app)
}
}
override fun onLongClick(position: Int, app: App) {
}
})
adapter?.let { adapter ->
if (adapter.getCurrentDownloadingAppsCount() == 0) {
val layoutManager = LinearLayoutManager(activity.get())
binding.rvContent.layoutManager = layoutManager
binding.rvContent.adapter = adapter
activity.get()?.let { activity ->
SingleToastUtil.show(activity, "Data Refreshed", Toast.LENGTH_LONG)
}
}
}
}
})
// Retrofit Api
public interface RetrofitAPI {
@GET("AmIAlive/{TabKey}")
Call<CheckService> isServiceRunning(
@Path("TabKey") String tabKey
);
@POST("PostLessonTrackingData/{TabKey}/")
Call<TrackingResponseModel> sendLessonTracking(
@Path("TabKey") String tabKey,
@Body LessonTracking LessonTracking
);
@Multipart
@Headers("Content-Type: application/json")
@POST("UploadFile/{fileNam}/")
Call<File> sendFileData(
@Path("fileNam") String fileName,
@Part("file") File file
);
}
// Preferences
public abstract class SharedPreferencesUtil {
private static final String NAME = "pref_name";
private SharedPreferences preferences;
SharedPreferencesUtil(Context context, boolean isAppDefaultPreferences) {
if (isAppDefaultPreferences) {
preferences = PreferenceManager.getDefaultSharedPreferences(context);
} else {
preferences = context.getSharedPreferences(NAME, Context.MODE_PRIVATE);
}
}
void setBoolean(String _key, boolean _value) {
if (_key == null) {
return;
}
if (preferences != null) {
SharedPreferences.Editor edit = preferences.edit();
edit.putBoolean(_key, _value);
edit.apply();
}
}
boolean getBoolean(String _key, boolean defaultValue) {
if (!preferences.contains(_key)) {
SharedPreferences.Editor edit = preferences.edit();
edit.putBoolean(_key, defaultValue);
edit.apply();
}
return preferences.getBoolean(_key, false);
}
void setString(String _key, String _value) {
if (_key == null) {
return;
}
if (preferences != null) {
SharedPreferences.Editor edit = preferences.edit();
edit.putString(_key, _value);
edit.apply();
}
}
protected String getString(String _key, String defaultValue) {
if (!preferences.contains(_key)) {
SharedPreferences.Editor edit = preferences.edit();
edit.putString(_key, defaultValue);
edit.apply();
}
return preferences.getString(_key, defaultValue);
}
void setInt(String _key, int _value) {
if (_key == null) {
return;
}
if (preferences != null) {
SharedPreferences.Editor edit = preferences.edit();
edit.putInt(_key, _value);
edit.apply();
}
}
int getInt(String _key, int defaultValue) {
if (!preferences.contains(_key)) {
SharedPreferences.Editor edit = preferences.edit();
edit.putInt(_key, defaultValue);
edit.apply();
}
return preferences.getInt(_key, defaultValue);
}
void setLong(String _key, long _value) {
if (_key == null) {
return;
}
if (preferences != null) {
SharedPreferences.Editor edit = preferences.edit();
edit.putLong(_key, _value);
edit.apply();
}
}
long getLong(String _key, long defaultValue) {
if (!preferences.contains(_key)) {
SharedPreferences.Editor edit = preferences.edit();
edit.putLong(_key, defaultValue);
edit.apply();
}
return preferences.getLong(_key, defaultValue);
}
}
// Advertisement
public class AdsUtil {
private InterstitialAd interstitialAd;
private Activity context;
public AdsUtil(Activity context) {
this.context = context;
MobileAds.initialize(context);
interstitialAd = new InterstitialAd(context);
interstitialAd.setAdUnitId(context.getResources().getString(R.string.admob_interstitial_ad));
}
public void loadBannerAd(AdView adView) {
adView.loadAd(new AdRequest.Builder()
.build());
}
public void loadInterstitialAd() {
interstitialAd.loadAd(new AdRequest.Builder()
.build());
}
private boolean isInterstitialLoaded() {
return interstitialAd.isLoaded();
}
public void showInterstitialAd() {
if (isInterstitialLoaded()) {
@SuppressLint("InflateParams")
View dialogLayout = LayoutInflater.from(context).inflate(R.layout.dialog_ads, null);
final Dialog dialog;
dialog = new Dialog(context, R.style.MyDialog);
dialog.setContentView(dialogLayout);
dialog.show();
new Handler().postDelayed(() -> {
if (MainActivity.activityState == STATE_RESUMED) {
interstitialAd.show();
}
dialog.dismiss();
}, 3000);
}
}
}