ইন্টিগ্রেট অ্যাসেট ডেলিভারি (কোটলিন এবং জাভা)

আপনার জাভা কোড থেকে আপনার অ্যাপের সম্পদ প্যাকগুলি অ্যাক্সেস করতে এই নির্দেশিকার ধাপগুলি ব্যবহার করুন।

কোটলিন এবং জাভার জন্য তৈরি করুন

আপনার প্রোজেক্টের অ্যান্ড্রয়েড অ্যাপ বান্ডেলে প্লে অ্যাসেট ডেলিভারি তৈরি করতে নিম্নলিখিত ধাপগুলি অনুসরণ করুন। এই ধাপগুলি সম্পাদন করার জন্য আপনাকে অ্যান্ড্রয়েড স্টুডিও ব্যবহার করার প্রয়োজন নেই।

  1. আপনার প্রোজেক্টের build.gradle ফাইলে থাকা Android Gradle প্লাগইনের সংস্করণটি 4.0.0 বা তার পরবর্তী সংস্করণে আপডেট করুন।

  2. আপনার প্রকল্পের শীর্ষ-স্তরের ডিরেক্টরিতে, সম্পদ প্যাকের জন্য একটি ডিরেক্টরি তৈরি করুন। এই ডিরেক্টরির নামটি সম্পদ প্যাকের নাম হিসাবে ব্যবহৃত হয়। সম্পদ প্যাকের নামগুলি অবশ্যই একটি অক্ষর দিয়ে শুরু হতে হবে এবং এতে কেবল অক্ষর, সংখ্যা এবং আন্ডারস্কোর থাকতে পারে।

  3. অ্যাসেট প্যাক ডিরেক্টরিতে, একটি build.gradle ফাইল তৈরি করুন এবং নিম্নলিখিত কোডটি যোগ করুন। অ্যাসেট প্যাকের নাম এবং শুধুমাত্র একটি ডেলিভারি টাইপ উল্লেখ করতে ভুলবেন না:

    খাঁজকাটা

    // In the asset pack's build.gradle file:
    plugins {
      id 'com.android.asset-pack'
    }
    
    assetPack {
        packName = "asset-pack-name" // Directory name for the asset pack
        dynamicDelivery {
            deliveryType = "[ install-time | fast-follow | on-demand ]"
        }
    }

    কোটলিন

    // In the asset pack's build.gradle.kts file:
    plugins {
      id("com.android.asset-pack")
    }
    
    assetPack {
      packName.set("asset-pack-name") // Directory name for the asset pack
      dynamicDelivery {
        deliveryType.set("[ install-time | fast-follow | on-demand ]")
      }
    }
  4. প্রজেক্টের অ্যাপ build.gradle ফাইলে, আপনার প্রজেক্টের প্রতিটি অ্যাসেট প্যাকের নাম নীচে দেখানো হিসাবে যোগ করুন:

    খাঁজকাটা

    // In the app build.gradle file:
    android {
        ...
        assetPacks = [":asset-pack-name", ":asset-pack2-name"]
    }

    কোটলিন

    // In the app build.gradle.kts file:
    android {
        ...
        assetPacks += listOf(":asset-pack-name", ":asset-pack2-name")
    }
  5. প্রজেক্টের settings.gradle ফাইলে, আপনার প্রজেক্টের সমস্ত অ্যাসেট প্যাকগুলি নীচে দেখানো হিসাবে অন্তর্ভুক্ত করুন:

    খাঁজকাটা

    // In the settings.gradle file:
    include ':app'
    include ':asset-pack-name'
    include ':asset-pack2-name'

    কোটলিন

    // In the settings.gradle.kts file:
    include(":app")
    include(":asset-pack-name")
    include(":asset-pack2-name")
  6. অ্যাসেট প্যাক ডিরেক্টরিতে, নিম্নলিখিত সাবডিরেক্টরি তৈরি করুন: src/main/assets

  7. src/main/assets ডিরেক্টরিতে সম্পদ রাখুন। আপনি এখানেও সাবডিরেক্টরি তৈরি করতে পারেন। আপনার অ্যাপের ডিরেক্টরি কাঠামো এখন নিচের মতো দেখাবে:

    • build.gradle
    • settings.gradle
    • app/
    • asset-pack-name /build.gradle
    • asset-pack-name /src/main/assets/ your-asset-directories
  8. Gradle দিয়ে Android অ্যাপ বান্ডেল তৈরি করুন । জেনারেট করা অ্যাপ বান্ডেলে, রুট-লেভেল ডিরেক্টরিতে এখন নিম্নলিখিতগুলি অন্তর্ভুক্ত থাকে:

    • asset-pack-name /manifest/AndroidManifest.xml : অ্যাসেট প্যাকের শনাক্তকারী এবং ডেলিভারি মোড কনফিগার করে
    • asset-pack-name /assets/ your-asset-directories : এমন ডিরেক্টরি যেখানে সম্পদ প্যাকের অংশ হিসেবে সরবরাহ করা সমস্ত সম্পদ থাকে

    গ্রেডল প্রতিটি অ্যাসেট প্যাকের জন্য ম্যানিফেস্ট তৈরি করে এবং আপনার জন্য assets/ ডিরেক্টরি আউটপুট করে।

  9. (ঐচ্ছিক) যদি আপনি দ্রুত-অনুসরণ এবং অন-ডিমান্ড ডেলিভারি ব্যবহার করার পরিকল্পনা করেন, তাহলে Play Asset Delivery Library অন্তর্ভুক্ত করুন।

    খাঁজকাটা

    implementation "com.google.android.play:asset-delivery:2.3.0"
    // For Kotlin use asset-delivery-ktx
    implementation "com.google.android.play:asset-delivery-ktx:2.3.0"

    কোটলিন

    implementation("com.google.android.play:asset-delivery:2.3.0")
    // For Kotlin use core-ktx
    implementation("com.google.android.play:asset-delivery-ktx:2.3.0")

  10. (ঐচ্ছিক) বিভিন্ন টেক্সচার কম্প্রেশন ফর্ম্যাট সমর্থন করার জন্য আপনার অ্যাপ বান্ডেল কনফিগার করুন।

Play Asset Delivery API এর সাথে ইন্টিগ্রেট করুন

Play Asset Delivery Java API অ্যাসেট প্যাক অনুরোধ, ডাউনলোড পরিচালনা এবং অ্যাসেট অ্যাক্সেস করার জন্য AssetPackManager ক্লাস প্রদান করে। প্রথমে আপনার প্রোজেক্টে Play Asset Delivery Library যোগ করতে ভুলবেন না।

আপনি যে অ্যাসেট প্যাকটি অ্যাক্সেস করতে চান তার ডেলিভারির ধরণ অনুসারে আপনি এই APIটি বাস্তবায়ন করেন। এই পদক্ষেপগুলি নিম্নলিখিত ফ্লোচার্টে দেখানো হয়েছে।

জাভা প্রোগ্রামিং ভাষার জন্য সম্পদ প্যাক প্রবাহ চিত্র

চিত্র ১. সম্পদ প্যাক অ্যাক্সেস করার জন্য প্রবাহ চিত্র

ইনস্টল-সময় ডেলিভারি

install-time হিসাবে কনফিগার করা সম্পদ প্যাকগুলি অ্যাপ লঞ্চের সাথে সাথেই উপলব্ধ হয়ে যায়। এই মোডে পরিবেশিত সম্পদগুলি অ্যাক্সেস করতে Java AssetManager API ব্যবহার করুন:

কোটলিন

import android.content.res.AssetManager
...
val context: Context = createPackageContext("com.example.app", 0)
val assetManager: AssetManager = context.assets
val stream: InputStream = assetManager.open("asset-name")

জাভা

import android.content.res.AssetManager;
...
Context context = createPackageContext("com.example.app", 0);
AssetManager assetManager = context.getAssets();
InputStream is = assetManager.open("asset-name");

দ্রুত-অনুসরণ এবং চাহিদা অনুযায়ী ডেলিভারি

নিম্নলিখিত বিভাগগুলিতে দেখানো হয়েছে কিভাবে ডাউনলোড করার আগে সম্পদ প্যাক সম্পর্কে তথ্য পেতে হয়, ডাউনলোড শুরু করার জন্য API-তে কীভাবে কল করতে হয় এবং তারপর ডাউনলোড করা প্যাকগুলি কীভাবে অ্যাক্সেস করতে হয়। এই বিভাগগুলি fast-follow এবং on-demand অ্যাসেট প্যাকগুলির ক্ষেত্রে প্রযোজ্য।

অবস্থা পরীক্ষা করুন

প্রতিটি অ্যাসেট প্যাক অ্যাপের অভ্যন্তরীণ স্টোরেজে একটি পৃথক ফোল্ডারে সংরক্ষণ করা হয়। একটি অ্যাসেট প্যাকের রুট ফোল্ডার নির্ধারণ করতে getPackLocation() পদ্ধতি ব্যবহার করুন। এই পদ্ধতিটি নিম্নলিখিত মানগুলি প্রদান করে:

ফেরত মান অবস্থা
একটি বৈধ AssetPackLocation অবজেক্ট অ্যাসেট প্যাক রুট ফোল্ডারটি assetsPath() এ তাৎক্ষণিক অ্যাক্সেসের জন্য প্রস্তুত।
null অজানা সম্পদ প্যাক বা সম্পদ উপলব্ধ নেই

সম্পদ প্যাক সম্পর্কে ডাউনলোড তথ্য পান

অ্যাসেট প্যাক আনার আগে অ্যাপগুলিকে ডাউনলোডের আকার প্রকাশ করতে হবে। ডাউনলোডের আকার এবং প্যাকটি ইতিমধ্যেই ডাউনলোড হচ্ছে কিনা তা নির্ধারণ করতে requestPackStates() অথবা getPackStates() পদ্ধতি ব্যবহার করুন।

কোটলিন

suspend fun requestPackStates(packNames: List<String>): AssetPackStates

জাভা

Task<AssetPackStates> getPackStates(List<String> packNames)

requestPackStates() হল একটি suspend ফাংশন যা একটি AssetPackStates অবজেক্ট ফেরত দেয় যখন getPackStates() হল একটি অ্যাসিঙ্ক্রোনাস পদ্ধতি যা একটি Task<AssetPackStates> ফেরত দেয়। একটি AssetPackStates অবজেক্টের packStates() পদ্ধতি একটি Map<String, AssetPackState> ফেরত দেয়। এই মানচিত্রে প্রতিটি অনুরোধকৃত অ্যাসেট প্যাকের অবস্থা রয়েছে, যার নাম দ্বারা কী করা হয়েছে:

কোটলিন

AssetPackStates#packStates(): Map<String, AssetPackState>

জাভা

Map<String, AssetPackState> AssetPackStates#packStates()

চূড়ান্ত অনুরোধটি নিম্নলিখিত দ্বারা দেখানো হয়েছে:

কোটলিন

const val assetPackName = "assetPackName"
coroutineScope.launch {
  try {
    val assetPackStates: AssetPackStates =
      manager.requestPackStates(listOf(assetPackName))
    val assetPackState: AssetPackState =
      assetPackStates.packStates()[assetPackName]
  } catch (e: RuntimeExecutionException) {
    Log.d("MainActivity", e.message)
  }
}

জাভা

final String assetPackName = "myasset";

assetPackManager
    .getPackStates(Collections.singletonList(assetPackName))
    .addOnCompleteListener(new OnCompleteListener<AssetPackStates>() {
        @Override
        public void onComplete(Task<AssetPackStates> task) {
            AssetPackStates assetPackStates;
            try {
                assetPackStates = task.getResult();
                AssetPackState assetPackState =
                    assetPackStates.packStates().get(assetPackName);
            } catch (RuntimeExecutionException e) {
                Log.d("MainActivity", e.getMessage());
                return;
            })

নিম্নলিখিত AssetPackState পদ্ধতিগুলি সম্পদ প্যাকের আকার, এখন পর্যন্ত ডাউনলোড করা পরিমাণ (যদি অনুরোধ করা হয়) এবং ইতিমধ্যে অ্যাপে স্থানান্তরিত পরিমাণ প্রদান করে:

একটি অ্যাসেট প্যাকের স্ট্যাটাস পেতে, status() পদ্ধতিটি ব্যবহার করুন, যা AssetPackStatus ক্লাসের একটি ধ্রুবক ক্ষেত্রের সাথে সঙ্গতিপূর্ণ একটি পূর্ণসংখ্যা হিসাবে স্ট্যাটাসটি ফেরত দেয়। একটি অ্যাসেট প্যাক যা এখনও ইনস্টল করা হয়নি তার স্ট্যাটাস AssetPackStatus.NOT_INSTALLED থাকে।

যদি কোন অনুরোধ ব্যর্থ হয়, তাহলে errorCode() পদ্ধতিটি ব্যবহার করুন, যার রিটার্ন মান AssetPackErrorCode ক্লাসের একটি ধ্রুবক ক্ষেত্রের সাথে মিলে যায়।

ইনস্টল করুন

প্রথমবারের মতো একটি অ্যাসেট প্যাক ডাউনলোড করতে requestFetch() অথবা fetch() পদ্ধতি ব্যবহার করুন অথবা সম্পূর্ণ করার জন্য একটি অ্যাসেট প্যাকের আপডেটের জন্য কল করুন:

কোটলিন

suspend fun AssetPackManager.requestFetch(packs: List<String>): AssetPackStates

জাভা

Task<AssetPackStates> fetch(List<String> packNames)

এই পদ্ধতিটি একটি AssetPackStates অবজেক্ট ফেরত পাঠায় যেখানে প্যাকগুলির একটি তালিকা এবং তাদের প্রাথমিক ডাউনলোড অবস্থা এবং আকার থাকে। যদি requestFetch() বা fetch() এর মাধ্যমে অনুরোধ করা একটি অ্যাসেট প্যাক ইতিমধ্যেই ডাউনলোড করা হয়, তাহলে ডাউনলোডের অবস্থা ফেরত পাঠানো হয় এবং কোনও অতিরিক্ত ডাউনলোড শুরু হয় না।

ডাউনলোডের অবস্থা পর্যবেক্ষণ করুন

অ্যাসেট প্যাকগুলির ইনস্টলেশনের অগ্রগতি ট্র্যাক করার জন্য আপনার একটি AssetPackStateUpdatedListener প্রয়োগ করা উচিত। প্রতিটি অ্যাসেট প্যাকের স্থিতি ট্র্যাক করার জন্য স্ট্যাটাস আপডেটগুলি প্রতিটি প্যাক অনুসারে ভাগ করা হয়। আপনার অনুরোধের জন্য অন্যান্য সমস্ত ডাউনলোড সম্পূর্ণ হওয়ার আগে আপনি উপলব্ধ অ্যাসেট প্যাকগুলি ব্যবহার শুরু করতে পারেন।

কোটলিন

fun registerListener(listener: AssetPackStateUpdatedListener)
fun unregisterListener(listener: AssetPackStateUpdatedListener)

জাভা

void registerListener(AssetPackStateUpdatedListener listener)
void unregisterListener(AssetPackStateUpdatedListener listener)

বড় ডাউনলোড

যদি ডাউনলোডটি ২০০ এমবি-র বেশি হয় এবং ব্যবহারকারী ওয়াই-ফাই ব্যবহার না করেন, তাহলে ব্যবহারকারী মোবাইল ডেটা সংযোগ ব্যবহার করে ডাউনলোড চালিয়ে যাওয়ার জন্য স্পষ্টভাবে সম্মতি না দেওয়া পর্যন্ত ডাউনলোড শুরু হবে না। একইভাবে, যদি ডাউনলোডটি বড় হয় এবং ব্যবহারকারী ওয়াই-ফাই ব্যবহার বন্ধ করে দেন, তাহলে ডাউনলোডটি স্থগিত করা হয় এবং মোবাইল ডেটা সংযোগ ব্যবহার করে এগিয়ে যাওয়ার জন্য স্পষ্টভাবে সম্মতি প্রয়োজন। একটি পজ করা প্যাকে WAITING_FOR_WIFI লেখা থাকে। ব্যবহারকারীকে সম্মতির জন্য অনুরোধ করার জন্য UI প্রবাহ ট্রিগার করতে, showConfirmationDialog() পদ্ধতি ব্যবহার করুন।

মনে রাখবেন যে যদি অ্যাপটি এই পদ্ধতিতে কল না করে, তাহলে ডাউনলোড স্থগিত থাকবে এবং ব্যবহারকারী যখন আবার Wi-Fi সংযোগে আসবে তখনই স্বয়ংক্রিয়ভাবে পুনরায় শুরু হবে।

প্রয়োজনীয় ব্যবহারকারীর নিশ্চিতকরণ

যদি কোনও প্যাকের REQUIRES_USER_CONFIRMATION স্ট্যাটাস থাকে, তাহলে ব্যবহারকারী showConfirmationDialog() দিয়ে দেখানো ডায়ালগটি গ্রহণ না করা পর্যন্ত ডাউনলোডটি এগোবে না। এই স্ট্যাটাসটি তখন ঘটতে পারে যখন Play অ্যাপটি শনাক্ত করতে পারে না—উদাহরণস্বরূপ, যদি অ্যাপটি সাইড-লোড করা থাকে। মনে রাখবেন যে এই ক্ষেত্রে showConfirmationDialog() কল করলে অ্যাপটি আপডেট হবে। আপডেটের পরে, আপনাকে আবার সম্পদের অনুরোধ করতে হবে।

নিচে একটি শ্রোতার বাস্তবায়নের উদাহরণ দেওয়া হল:

কোটলিন

private val activityResultLauncher = registerForActivityResult(
    ActivityResultContracts.StartIntentSenderForResult()
) { result ->
    if (result.resultCode == RESULT_OK) {
        Log.d(TAG, "Confirmation dialog has been accepted.")
    } else if (result.resultCode == RESULT_CANCELED) {
        Log.d(TAG, "Confirmation dialog has been denied by the user.")
    }
}

assetPackManager.registerListener { assetPackState ->
  when(assetPackState.status()) {
    AssetPackStatus.PENDING -> {
      Log.i(TAG, "Pending")
    }
    AssetPackStatus.DOWNLOADING -> {
      val downloaded = assetPackState.bytesDownloaded()
      val totalSize = assetPackState.totalBytesToDownload()
      val percent = 100.0 * downloaded / totalSize

      Log.i(TAG, "PercentDone=" + String.format("%.2f", percent))
    }
    AssetPackStatus.TRANSFERRING -> {
      // 100% downloaded and assets are being transferred.
      // Notify user to wait until transfer is complete.
    }
    AssetPackStatus.COMPLETED -> {
      // Asset pack is ready to use. Start the game.
    }
    AssetPackStatus.FAILED -> {
      // Request failed. Notify user.
      Log.e(TAG, assetPackState.errorCode())
    }
    AssetPackStatus.CANCELED -> {
      // Request canceled. Notify user.
    }
    AssetPackStatus.WAITING_FOR_WIFI,
    AssetPackStatus.REQUIRES_USER_CONFIRMATION -> {
      if (!confirmationDialogShown) {
        assetPackManager.showConfirmationDialog(activityResultLauncher);
        confirmationDialogShown = true
      }
    }
    AssetPackStatus.NOT_INSTALLED -> {
      // Asset pack is not downloaded yet.
    }
    AssetPackStatus.UNKNOWN -> {
      Log.wtf(TAG, "Asset pack status unknown")
    }
  }
}

জাভা

assetPackStateUpdateListener = new AssetPackStateUpdateListener() {
    private final ActivityResultLauncher<IntentSenderRequest> activityResultLauncher =
      registerForActivityResult(
          new ActivityResultContracts.StartIntentSenderForResult(),
          new ActivityResultCallback<ActivityResult>() {
            @Override
            public void onActivityResult(ActivityResult result) {
              if (result.getResultCode() == RESULT_OK) {
                Log.d(TAG, "Confirmation dialog has been accepted.");
              } else if (result.getResultCode() == RESULT_CANCELED) {
                Log.d(TAG, "Confirmation dialog has been denied by the user.");
              }
            }
          });

    @Override
    public void onStateUpdate(AssetPackState assetPackState) {
      switch (assetPackState.status()) {
        case AssetPackStatus.PENDING:
          Log.i(TAG, "Pending");
          break;

        case AssetPackStatus.DOWNLOADING:
          long downloaded = assetPackState.bytesDownloaded();
          long totalSize = assetPackState.totalBytesToDownload();
          double percent = 100.0 * downloaded / totalSize;

          Log.i(TAG, "PercentDone=" + String.format("%.2f", percent));
          break;

        case AssetPackStatus.TRANSFERRING:
          // 100% downloaded and assets are being transferred.
          // Notify user to wait until transfer is complete.
          break;

        case AssetPackStatus.COMPLETED:
          // Asset pack is ready to use. Start the game.
          break;

        case AssetPackStatus.FAILED:
          // Request failed. Notify user.
          Log.e(TAG, assetPackState.errorCode());
          break;

        case AssetPackStatus.CANCELED:
          // Request canceled. Notify user.
          break;

        case AssetPackStatus.WAITING_FOR_WIFI:
        case AssetPackStatus.REQUIRES_USER_CONFIRMATION:
          if (!confirmationDialogShown) {
            assetPackManager.showConfirmationDialog(activityResultLauncher);
            confirmationDialogShown = true;
          }
          break;

        case AssetPackStatus.NOT_INSTALLED:
          // Asset pack is not downloaded yet.
          break;
        case AssetPackStatus.UNKNOWN:
          Log.wtf(TAG, "Asset pack status unknown")
          break;
      }
    }
}

বিকল্পভাবে, আপনি বর্তমান ডাউনলোডগুলির অবস্থা জানতে getPackStates() পদ্ধতি ব্যবহার করতে পারেন। AssetPackStates ডাউনলোডের অগ্রগতি, ডাউনলোডের অবস্থা এবং যেকোনো ব্যর্থতার ত্রুটি কোড থাকে।

সম্পদ প্যাকগুলি অ্যাক্সেস করুন

ডাউনলোডের অনুরোধটি COMPLETED অবস্থায় পৌঁছানোর পরে আপনি ফাইল সিস্টেম কল ব্যবহার করে একটি সম্পদ প্যাক অ্যাক্সেস করতে পারেন। সম্পদ প্যাকের রুট ফোল্ডার পেতে getPackLocation() পদ্ধতি ব্যবহার করুন।

সম্পদগুলি সম্পদ প্যাক রুট ডিরেক্টরির মধ্যে assets ডিরেক্টরিতে সংরক্ষণ করা হয়। আপনি সুবিধাজনক পদ্ধতি assetsPath() ব্যবহার করে assets ডিরেক্টরিতে যাওয়ার পথ পেতে পারেন। একটি নির্দিষ্ট সম্পদের পথ পেতে নিম্নলিখিত পদ্ধতিটি ব্যবহার করুন:

কোটলিন

private fun getAbsoluteAssetPath(assetPack: String, relativeAssetPath: String): String? {
    val assetPackPath: AssetPackLocation =
      assetPackManager.getPackLocation(assetPack)
      // asset pack is not ready
      ?: return null

    val assetsFolderPath = assetPackPath.assetsPath()
    // equivalent to: FilenameUtils.concat(assetPackPath.path(), "assets")
    return FilenameUtils.concat(assetsFolderPath, relativeAssetPath)
}

জাভা

private String getAbsoluteAssetPath(String assetPack, String relativeAssetPath) {
    AssetPackLocation assetPackPath = assetPackManager.getPackLocation(assetPack);

    if (assetPackPath == null) {
        // asset pack is not ready
        return null;
    }

    String assetsFolderPath = assetPackPath.assetsPath();
    // equivalent to: FilenameUtils.concat(assetPackPath.path(), "assets");
    String assetPath = FilenameUtils.concat(assetsFolderPath, relativeAssetPath);
    return assetPath;
}

অন্যান্য Play অ্যাসেট ডেলিভারি API পদ্ধতি

আপনার অ্যাপে ব্যবহার করার জন্য কিছু অতিরিক্ত API পদ্ধতি নিচে দেওয়া হল।

অনুরোধ বাতিল করুন

একটি সক্রিয় সম্পদ প্যাক অনুরোধ বাতিল করতে cancel() ব্যবহার করুন। মনে রাখবেন যে এই অনুরোধটি একটি সর্বোত্তম প্রচেষ্টার অপারেশন।

একটি অ্যাসেট প্যাক সরান

একটি অ্যাসেট প্যাক অপসারণের সময়সূচী নির্ধারণ করতে requestRemovePack() অথবা removePack() ব্যবহার করুন।

একাধিক সম্পদ প্যাকের অবস্থান পান

একাধিক অ্যাসেট প্যাকের স্ট্যাটাস জানতে getPackLocations() ব্যবহার করুন, যা অ্যাসেট প্যাক এবং তাদের অবস্থানের একটি মানচিত্র প্রদান করে। getPackLocations() দ্বারা প্রদত্ত মানচিত্রে প্রতিটি প্যাকের জন্য একটি এন্ট্রি রয়েছে যা বর্তমানে ডাউনলোড করা এবং আপ-টু-ডেট রয়েছে।

পরবর্তী ধাপ

স্থানীয়ভাবে এবং Google Play থেকে Play অ্যাসেট ডেলিভারি পরীক্ষা করুন