اذهب إلى المحتوى

طلب الأذونات من المستخدم واستخدام موارد الهاتف في تطبيقات أندرويد


أحمد النوبي

يتمتع نظام تشغيل أندرويد بوسائل أمان خاصة كي تحمي مستخدميه من مخاطر سرقة البيانات أو العبث في الهاتف دون علم المستخدم، وذلك عن طريق عزل كل تطبيق داخل صندوق خاص به لا يستطيع أن يرى أو يتعامل مع الموارد الخاصة بالتطبيقات الأخرى ما لم تكن لديه الأذونات الخاصة لذلك وموافقة المستخدم عليها.
بشكل افتراضي لا يملك التطبيق أي أذونات للوصول لأي شيء خارج التطبيق كاستخدام الكاميرا أو الاتصال بأحد جهات الاتصال في الهاتف ما لم يتم تضمين الأذونات الخاصة بذلك داخل التطبيق ثم موافقة المستخدم عليه.

android-11.png

فعند تنصيب التطبيق من متجر Play يطلب من المستخدم الموافقة على أذونات محددة يحتاجها التطبيق كي يعمل وبعد الموافقة عليها يبدأ التطبيق في التحميل والتنصيب، لذا يُنصح دائمًا بقراءة هذه الأذونات بعناية والتأكد من احتياج التطبيق لها ليؤدي وظيفته المطلوبة دون الخوف من الوصول لأشياء لا يحتاجها وذلك لكثرة وجود البرمجيات الخبيثة التي تستغل موافقتك على الأذونات للعبث بموارد ومعلومات هاتفك.

تطبيق 1

في هذا المثال سنتعرف على كيفية صنع تطبيق يطلب الوصول إلى أذونات معينة من المستخدم كي يؤدي وظيفته، يقوم هذا التطبيق بدور المصباح اليدوي حيث يُضيء الضوء الخاص بالكاميرا (Flashlight) بشكل برمجي عند الضغط على زر بداخله.

001.png

كما نفعل دومًا نبدأ بصنع واجهة المستخدم الخاصة بالتطبيق، وتتكون من زر من النوع ImageButton -وهو زر يستطيع أن يحتوي على صورة -لتصبح الشيفرة الخاصة بملف activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
>

    <ImageButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/power_on"
        android:id="@+id/btn"
        android:background="@null"/>
</LinearLayout>

بعد ذلك نقوم بالتعديل في ملف AndroidManifest.xml وإضافة الأذونات المطلوبة لكي نستطيع التحكم في ضوء الكاميرا.

<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.Camera"/>

باستخدام الوسم الخاص بالأذونات <uses-permissions> وتحديد الاسم الخاص به في الخاصية android:name، وأيضًا إضافة الوسم <uses-feature> لإعلام الهاتف أن التطبيق يحتاج إلى وجود كاميرا كي يعمل.
ويوجد العديد من الأذونات التي تمكنك من القيام بالعديد من المهام من داخل التطبيق كالاتصال بالإنترنت أو القراءة والكتابة في الذاكرة ويمكنك التعرف عليها من هنا ويتم تعريفها داخل الملف AndroidManifest.xml كما سبق.
إذا لم يتم تعريف الأذونات المستخدمة في التطبيق داخل ملف AndroidManifest.xml فإن التطبيق لن يعمل.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="apps.noby.flashlight">

    <uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.Camera"/>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

ننتقل الآن إلى كتابة شيفرة التحكم الخاصة بالتطبيق في ملف MainActivity.java وتنقسم إلى
1-التأكد من توفر ضوء للكاميرا في الهاتف أولًا.

if(!getApplicationContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH))
    finish();

وإذا لم يتواجد ضوء الكاميرا في الهاتف فسيتم غلق التطبيق في الحال.
2-الحصول على كائن يُمكننا من التحكم في ضوء الكاميرا وذلك عن طريق فتح الكاميرا ثم الحصول على الخصائص والمعاملات الخاصة بها وتغيير المتغير الخاص بضوء الكاميرا.

camera = Camera.open();
param = camera.getParameters();

3-برمجة الزر المتواجد في الواجهة الرئيسية لكي يُضيء ضوء الكاميرا عند الضغط عليه ويغلقه عن الضغط مرة أخرى.

 btn = (ImageButton) findViewById(R.id.btn);

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(!isTorchON){
                    turnONTorch();
                }else
                    turnOFFTorch();
            }
        });

لتُصبح شيفرة التحكم الرئيسية هي

package apps.noby.flashlight;

import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageButton;

public class MainActivity extends Activity {


    ImageButton btn;
    boolean isTorchON;
    Camera camera;
    private Parameters param;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        btn = (ImageButton) findViewById(R.id.btn);

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(!isTorchON){
                    turnONTorch();
                }else
                    turnOFFTorch();
            }
        });
    }


    @Override
protected void onStart() {
    super. onStart();

    if(!getApplicationContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH))
        finish();

    if (camera == null) {
        isTorchON = false;
        camera = Camera.open();
        param = camera.getParameters();
    }
}


    @Override
    protected void onPause() {
        super.onPause();
        if (camera != null) {
            camera.release();
            camera = null;
        }
    }
}

يتبقى تعريف ما هي الوظيفة الخاصة بالدالة turnONTorch() وTurnOFFTorch().
تقوم الدالة turnONTorch() بتشغيل الضوء وذلك عن طريق تمرير المعامل FLASH_MODE_TORCH كإحدى معاملات الكاميرا ثم تفعيل الكاميرا بالمعاملات الجديدة عند استدعاء startPreview().

    private void turnONTorch() {

        param.setFlashMode(Parameters.FLASH_MODE_TORCH);
        camera.setParameters(param);
        camera.startPreview();
        isTorchON = true;
        btn.setImageResource(R.drawable.power_off);

    }

وبالمثل نبني turnOFFTorch() كي يتم إطفاء الضوء.

    private void turnOFFTorch() {

        param.setFlashMode(Parameters.FLASH_MODE_OFF);
        camera.setParameters(param);
        camera.stopPreview();
        isTorchON = false;
        btn.setImageResource(R.drawable.power_on);

    }

لتُصبح الشيفرة النهائية لملف MainActivity.java

package apps.noby.flashlight;

import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageButton;

public class MainActivity extends Activity {
ImageButton btn;
boolean isTorchON;
Camera camera;
private Parameters param;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);


    btn = (ImageButton) findViewById(R.id.btn);

    btn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if(!isTorchON){
                turnONTorch();
            }else
                turnOFFTorch();
        }
    });
}

private void turnONTorch() {

    param.setFlashMode(Parameters.FLASH_MODE_TORCH);
    camera.setParameters(param);
    camera.startPreview();
    isTorchON = true;
    btn.setImageResource(R.drawable.power_off);

}

private void turnOFFTorch() {

    param.setFlashMode(Parameters.FLASH_MODE_OFF);
    camera.setParameters(param);
    camera.stopPreview();
    isTorchON = false;
    btn.setImageResource(R.drawable.power_on);

}

@Override
protected void onStart() {
    super. onStart();

    if(! getApplicationContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH))
        finish();

    if (camera == null) {
        isTorchON = false;
        camera = Camera.open();
        param = camera.getParameters();
    }
}

@Override
protected void onPause() {
    super.onPause();
    if (camera != null) {
        camera.release();
        camera = null;


    }
    }
}

بعد ذلك نقوم بتجربة التطبيق على هاتف حقيقي وليس المحاكي حيث لا يمكنه محاكاة إضاءة ضوء الكاميرا.

تطبيق 2

بعد الإصدار السادس لنظام تشغيل أندرويد (Android Marshmallow) تم تعريف نموذج جديد للتعامل مع الأذونات داخل التطبيق حيث كان النموذج القديم يتطلب موافقة المستخدم على كافة الأذونات الخاصة بالتطبيق قبل تحميله على الهاتف أو رفضها وعدم تحميل التطبيق. أما الآن فأصبح بإمكان المستخدم تحميل وتنصيب التطبيق على الهاتف دون الموافقة على الأذونات إلا عند الحاجة إليها لكي تقوم بوظيفة ما، كما يمكن الموافقة على بعض الأذونات ورفض الأخرى (قد يؤدي ذلك إلى عدم عمل التطبيق بشكله الكامل) ويمكن بعد ذلك الموافقة عليها مما يعطي المستخدم القدرة على التحكم في التطبيق من داخل الإعدادات الخاصة بالتطبيق.

002.png

وتنقسم الأذونات إلى قسمين:

  1. الأذونات العادية وهي التي لا تهدد خصوصية المستخدم، ويتم التعامل معها دون طلب الأذن عند الحاجة إليها يكفي فقط طلب الأذن عند التحميل من المتجر مرة واحدة.
  2. الأذونات الخطيرة وهي التي قد تصل إلى بيانات خاصة بالمستخدم وتحتاج إلى انتباه المستخدم للتطبيق وطلب الأذن منه عندما استخدامها من قبل التطبيق.
    سنتعامل في هذا التطبيق مع أحد هذه الأذونات الخطيرة، حيث سنقوم بصنع تطبيق يمكنه الاتصال مباشرة برقم محدد من قبل المستخدم ويُعتبر الاتصال من الأذونات الخطيرة وذلك لأنه قد يُعرض المستخدم لتكاليف خاصة بالاتصال.

نبدأ أولًا بصنع واجهة المستخدم وهي تتكون من عنصر EditText كي يكتب المستخدم بداخله الرقم الذي يرغب في الاتصال به وزر للضغط عليه حتى يقوم التطبيق بالاتصال بهذا الرقم.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:layout_margin="16dp">

    <EditText
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:hint="Enter Phone Number"
        android:id="@+id/ph_num"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Call"
        android:id="@+id/call_btn"/>
</LinearLayout>

بعد ذلك نحدد الأذونات الخاصة بالتطبيق في AndroidManifest.xml وهنا هو

<uses-permission android:name="android.permission.CALL_PHONE"/>

ويتم تضمينه داخل الملف كما سبق.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="apps.noby.dailer">

    <uses-permission android:name="android.permission.CALL_PHONE"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

الآن يتبقى برمجة الزر كي يقوم بالاتصال ويتم التحكم في ذلك عن طريق استخدام Intent وتمرير له ACTION_CALL لتحديد وظيفته وهي الاتصال، ثم تحديد البيانات التي نرغب في إرسالها بداخل هذا الـ Intent وهي رقم الهاتف الذي تم إدخاله من قبل المستخدم داخل EditText وتسبقه “tel:” لإعلام نظام التشغيل بأنه رقم هاتف.

     String num = et.getText().toString();
     Intent intent =new Intent(Intent.ACTION_CALL);
     intent.setData(Uri.parse("tel:" + num)) ;

قبل القيام بعملية الاتصال يجب أولًا التأكد من حصول التطبيق على الأذونات الخاصة بالاتصال من المستخدم سواء عند التنصيب في الإصدارات القديمة أو طلبها في الإصدارات الأحدث.

call_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String num = et.getText().toString();
                Intent intent =new Intent(Intent.ACTION_CALL);
                intent.setData(Uri.parse("tel:" + num)) ;

                    if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {

                        ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.CALL_PHONE},CALL_PHONE_PERMISSIONS_REQUEST);
                    }
                    else
                        startActivity(intent);
            }
        });

للتحقق من الأذونات نستخدم التابع checkSelfPermission() فإذا كان لم يتم الحصول عليه نطلب من المستخدم الموافقة عليه باستخدام التابع requestPermissions() غير ذلك نقوم بالاتصال.
عند طلب الحصول على إذن محدد من المستخدم يظهر أمام المستخدم مربع يطلب منه الموافقة أو الرفض ويقوم بتسجيل اختيار المستخدم لذلك ثم يستدعي التابع onRequestPermissionsResult() بشكل تلقائي ونقوم بداخله من التأكد من موافقة المستخدم على الأذن وإظهار رسالة بذلك.


003.png

    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {

        if (requestCode == CALL_PHONE_PERMISSIONS_REQUEST) {
            if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(this, "Call Phones permission granted", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this, "Call Phones permission denied", Toast.LENGTH_SHORT).show();
            }
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
}

لتُصبح الشيفرة الكاملة لهذا التطبيق في ملف MainActivity.java هي

package apps.noby.dailer;

import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends Activity {

    private static final int CALL_PHONE_PERMISSIONS_REQUEST = 3;
    private EditText et;
    private Button call_btn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        et = (EditText) findViewById(R.id.ph_num);
        call_btn = (Button) findViewById(R.id.call_btn);

        call_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String num = et.getText().toString();
                Intent intent =new Intent(Intent.ACTION_CALL);
                intent.setData(Uri.parse("tel:" + num)) ;

                    if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {

                        ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.CALL_PHONE},CALL_PHONE_PERMISSIONS_REQUEST);
                    }
                    else
                        startActivity(intent);
            }
        });

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {

        if (requestCode == CALL_PHONE_PERMISSIONS_REQUEST) {
            if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(this, "Call Phones permission granted", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this, "Call Phones permission denied", Toast.LENGTH_SHORT).show();
            }
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
}

لاحظ أنه إذا كان الهاتف يعمل بإصدار سابق لـ Android Marshmallow فسيتم الحصول على الموافقة على كافة الأذونات عند التحميل من المتجر كما كان يحدث سابقًا.
يمكننا الآن تجربة التطبيق على المحاكي أو على هاتف حقيقي.

004.png

يجب عليك مراعاة عدم إجهاد المستخدم بالعديد من الأذونات التي لا حاجة لها داخل تطبيقك حتى لا يضطر المستخدم إلى إزالة تطبيقك والتخلص منه.
لذا يجب الاهتمام بتقديم تجربة استخدام جيدة، ففي العديد من الأحيان لا يكون خيار طلب الأذونات من المستخدم والقيام بالمهمة من داخل تطبيقك هو الخيار الأفضل، فقد يكون الأفضل استخدام الـ intent وطلب الوظيفة من تطبيق آخر للقيام بها.
مثال على ذلك يمكننا تعديل التطبيق السابق وإزالة الأذن الخاص بالاتصال من التطبيق، وتعديل الشيفرة في MainActivty.java كي تقوم بنقل مهمة الاتصال من التطبيق نفسه إلى أحد تطبيقات الاتصال المتواجدة بالهاتف

package apps.noby.dailer;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

public class MainActivity extends Activity {

    private EditText et;
    private Button call_btn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        et = (EditText) findViewById(R.id.ph_num);
        call_btn = (Button) findViewById(R.id.call_btn);

        call_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String num = et.getText().toString();
                Intent intent =new Intent(Intent.ACTION_DIAL);
                intent.setData(Uri.parse("tel:" + num)) ;
                startActivity(intent);
            }
        });

    }

}

عند تجربة التطبيق على المحاكي ينقلنا إلى تطبيق الاتصال الخاص بالهاتف مع إدخال رقم الهاتف والانتظار لاتصال المستخدم بالرقم.

005.png

لا توجد طريقة أفضل من الأخرى، بل يعتمد ذلك على مدى اهتمامك بالحصول على التحكم الكامل بكافة التفاصيل لأداء المهمة عند ذلك قم بطلب الأذن من المستخدم وإذا رفض المستخدم ذلك فلن يعمل تطبيقك، أو ترك الوظيفة إلى تطبيق آخر وتكتفي فقط باستقبال أو إرسال البيانات منه أو إليه ولن تحتاج إلى الحصول على أي أذونات من المستخدم لكنك ستنقل المستخدم إلى تطبيق آخر لا تعلم عنه شيئًا قد يضر تجربة الاستخدام الخاصة بتطبيقك.

بهذا نكون قد وصلنا إلى نهاية هذا الدرس، في انتظار تجربتكم وآرائكم.

Icons.zip


تفاعل الأعضاء

أفضل التعليقات



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...